Added ability to move students to another class.
This commit is contained in:
parent
e81afd3fce
commit
3a668c9e11
|
@ -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);
|
||||
|
|
|
@ -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}`)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue