Added ability to move students to another class.

This commit is contained in:
Andrew Lalis 2025-01-17 21:32:40 -05:00
parent e81afd3fce
commit 3a668c9e11
3 changed files with 117 additions and 9 deletions

View File

@ -64,6 +64,7 @@ void registerApiEndpoints(PathHandler handler) {
handler.addMapping(Method.GET, STUDENT_PATH, &getStudent);
handler.addMapping(Method.PUT, STUDENT_PATH, &updateStudent);
handler.addMapping(Method.DELETE, STUDENT_PATH, &deleteStudent);
handler.addMapping(Method.PUT, STUDENT_PATH ~ "/class", &moveStudentToOtherClass);
handler.addMapping(Method.GET, STUDENT_PATH ~ "/entries", &getStudentEntries);
handler.addMapping(Method.GET, STUDENT_PATH ~ "/overview", &getStudentOverview);
@ -394,15 +395,24 @@ void getEntries(ref HttpRequestContext ctx) {
// Find the student object this entry belongs to, then add it to their list.
ulong studentId = row.peek!ulong(5);
bool studentFound = false;
foreach (idx, student; students) {
if (student.id == studentId) {
studentObjects[idx].object["entries"].object[dateStr] = entry;
foreach (idx, studentObj; studentObjects) {
if (studentObj.object["id"].uinteger == studentId) {
studentObj.object["entries"].object[dateStr] = entry;
studentFound = true;
break;
}
}
if (!studentFound) {
throw new Exception("Failed to find student.");
// The student isn't in our list of original students from the class, so it's a student who's moved to another.
JSONValue obj = JSONValue.emptyObject;
obj.object["id"] = JSONValue(studentId);
obj.object["deskNumber"] = JSONValue(row.peek!ushort(7));
obj.object["name"] = JSONValue(row.peek!string(6));
obj.object["removed"] = JSONValue(row.peek!bool(8));
obj.object["entries"] = JSONValue.emptyObject;
obj.object["entries"].object[dateStr] = entry;
obj.object["score"] = JSONValue(null);
studentObjects ~= obj;
}
}
@ -411,9 +421,9 @@ void getEntries(ref HttpRequestContext ctx) {
foreach (studentId, score; scores) {
JSONValue scoreValue = score.isNull ? JSONValue(null) : JSONValue(score.value);
bool studentFound = false;
foreach (idx, student; students) {
if (studentId == student.id) {
studentObjects[idx].object["score"] = scoreValue;
foreach (studentObj; studentObjects) {
if (studentObj.object["id"].uinteger == studentId) {
studentObj.object["score"] = scoreValue;
studentFound = true;
break;
}
@ -737,6 +747,37 @@ private Optional!double calculateScore(
return Optional!double.of(score);
}
void moveStudentToOtherClass(ref HttpRequestContext ctx) {
auto db = getDb();
User user = getUserOrThrow(ctx, db);
auto student = getStudentOrThrow(ctx, db, user);
struct Payload {
ulong classId;
}
Payload payload = readJsonPayload!(Payload)(ctx);
if (payload.classId == student.classId) {
return; // Quit if the student is already in the desired class.
}
// Check that the desired class exists, and belongs to the user.
bool newClassIdValid = canFind(
db,
"SELECT id FROM classroom_compliance_class WHERE user_id = ? and id = ?",
user.id, payload.classId
);
if (!newClassIdValid) {
ctx.response.status = HttpStatus.BAD_REQUEST;
ctx.response.writeBodyString("Invalid class was selected.");
return;
}
// All good, so update the student's class to the desired one, and reset their desk.
db.execute(
"UPDATE classroom_compliance_student SET class_id = ?, desk_number = 0 WHERE id = ?",
payload.classId,
student.id
);
// We just return 200 OK, no response body.
}
void getStudentEntries(ref HttpRequestContext ctx) {
auto db = getDb();
User user = getUserOrThrow(ctx, db);

View File

@ -140,6 +140,14 @@ export class ClassroomComplianceAPIClient extends APIClient {
return super.put(`/classes/${classId}/students/${studentId}`, data)
}
moveStudentToOtherClass(
classId: number,
studentId: number,
newClassId: number,
): APIResponse<void> {
return super.put(`/classes/${classId}/students/${studentId}/class`, { classId: newClassId })
}
deleteStudent(classId: number, studentId: number): APIResponse<void> {
return super.delete(`/classes/${classId}/students/${studentId}`)
}

View File

@ -1,12 +1,15 @@
<script setup lang="ts">
import { showAlert } from '@/alerts';
import { APIError } from '@/api/base';
import {
ClassroomComplianceAPIClient,
type Class,
type Student,
type StudentDataPayload,
} from '@/api/classroom_compliance'
import ConfirmDialog from '@/components/ConfirmDialog.vue';
import { useAuthStore } from '@/stores/auth'
import { onMounted, ref, type Ref } from 'vue'
import { onMounted, ref, useTemplateRef, type Ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const props = defineProps<{
@ -15,9 +18,15 @@ const props = defineProps<{
const authStore = useAuthStore()
const route = useRoute()
const router = useRouter()
const apiClient = new ClassroomComplianceAPIClient(authStore)
const cls: Ref<Class | null> = ref(null)
const student: Ref<Student | null> = ref(null)
const apiClient = new ClassroomComplianceAPIClient(authStore)
const moveClassDialog = useTemplateRef('move-class-dialog')
const moveClassConfirmDialog = useTemplateRef('moveClassConfirmDialog')
const moveClassDialogClassChoices: Ref<Class[]> = ref([])
const moveClassDialogClassSelection: Ref<Class | null> = ref(null)
interface StudentFormData {
name: string
@ -85,6 +94,29 @@ function resetForm() {
}
router.back()
}
// Move-Class-Dialog actions:
async function showMoveClassDialog() {
// Populate the list of available classes to all but the student's current class.
const classes = await apiClient.getClasses().handleErrorsWithAlert()
if (classes === null) return
moveClassDialogClassChoices.value = classes.filter(c => c.id !== cls.value?.id)
moveClassDialogClassSelection.value = null
moveClassDialog.value?.showModal()
}
async function doMoveClass() {
if (!cls.value || !student.value || !moveClassDialogClassSelection.value) return
const choice = await moveClassConfirmDialog.value?.show()
if (!choice) return
const newClassId = moveClassDialogClassSelection.value.id
const result = await apiClient.moveStudentToOtherClass(cls.value.id, student.value.id, newClassId)
if (result instanceof APIError) {
await showAlert(result.message)
} else {
await router.replace(`/classroom-compliance/classes/${newClassId}/students/${student.value.id}`)
}
}
</script>
<template>
<div v-if="cls">
@ -114,7 +146,34 @@ function resetForm() {
<div class="button-bar">
<button type="submit">Save</button>
<button type="reset">Cancel</button>
<button type="button" @click="showMoveClassDialog()">Move to another class</button>
</div>
</form>
<!-- Dialog for moving the student to another class. -->
<dialog id="move-class-dialog" ref="move-class-dialog">
<p>
Select a class to move them to below:
</p>
<select v-model="moveClassDialogClassSelection">
<option v-for="c in moveClassDialogClassChoices" :key="c.id" v-text="'Class ' + c.number" :value="c"></option>
</select>
<p v-if="moveClassDialogClassSelection">
Will move {{ student?.name }} from class {{ cls.number }} to class {{ moveClassDialogClassSelection.number }}.
</p>
<div>
<button type="button" id="move-class-dialog-submit-button" @click="doMoveClass()">Submit</button>
<button type="button" id="move-class-dialog-cancel-button" @click="moveClassDialog?.close()">Cancel</button>
</div>
</dialog>
<ConfirmDialog ref="moveClassConfirmDialog">
<p>
Are you sure that you want to move {{ student?.name }} to class {{ moveClassDialogClassSelection?.number }}?
Their desk number will be reset. All current entries in their previous class will be retained.
</p>
</ConfirmDialog>
</div>
</template>