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.GET, STUDENT_PATH, &getStudent);
|
||||||
handler.addMapping(Method.PUT, STUDENT_PATH, &updateStudent);
|
handler.addMapping(Method.PUT, STUDENT_PATH, &updateStudent);
|
||||||
handler.addMapping(Method.DELETE, STUDENT_PATH, &deleteStudent);
|
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 ~ "/entries", &getStudentEntries);
|
||||||
handler.addMapping(Method.GET, STUDENT_PATH ~ "/overview", &getStudentOverview);
|
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.
|
// Find the student object this entry belongs to, then add it to their list.
|
||||||
ulong studentId = row.peek!ulong(5);
|
ulong studentId = row.peek!ulong(5);
|
||||||
bool studentFound = false;
|
bool studentFound = false;
|
||||||
foreach (idx, student; students) {
|
foreach (idx, studentObj; studentObjects) {
|
||||||
if (student.id == studentId) {
|
if (studentObj.object["id"].uinteger == studentId) {
|
||||||
studentObjects[idx].object["entries"].object[dateStr] = entry;
|
studentObj.object["entries"].object[dateStr] = entry;
|
||||||
studentFound = true;
|
studentFound = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!studentFound) {
|
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) {
|
foreach (studentId, score; scores) {
|
||||||
JSONValue scoreValue = score.isNull ? JSONValue(null) : JSONValue(score.value);
|
JSONValue scoreValue = score.isNull ? JSONValue(null) : JSONValue(score.value);
|
||||||
bool studentFound = false;
|
bool studentFound = false;
|
||||||
foreach (idx, student; students) {
|
foreach (studentObj; studentObjects) {
|
||||||
if (studentId == student.id) {
|
if (studentObj.object["id"].uinteger == studentId) {
|
||||||
studentObjects[idx].object["score"] = scoreValue;
|
studentObj.object["score"] = scoreValue;
|
||||||
studentFound = true;
|
studentFound = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -737,6 +747,37 @@ private Optional!double calculateScore(
|
||||||
return Optional!double.of(score);
|
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) {
|
void getStudentEntries(ref HttpRequestContext ctx) {
|
||||||
auto db = getDb();
|
auto db = getDb();
|
||||||
User user = getUserOrThrow(ctx, db);
|
User user = getUserOrThrow(ctx, db);
|
||||||
|
|
|
@ -140,6 +140,14 @@ export class ClassroomComplianceAPIClient extends APIClient {
|
||||||
return super.put(`/classes/${classId}/students/${studentId}`, data)
|
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> {
|
deleteStudent(classId: number, studentId: number): APIResponse<void> {
|
||||||
return super.delete(`/classes/${classId}/students/${studentId}`)
|
return super.delete(`/classes/${classId}/students/${studentId}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { showAlert } from '@/alerts';
|
||||||
|
import { APIError } from '@/api/base';
|
||||||
import {
|
import {
|
||||||
ClassroomComplianceAPIClient,
|
ClassroomComplianceAPIClient,
|
||||||
type Class,
|
type Class,
|
||||||
type Student,
|
type Student,
|
||||||
type StudentDataPayload,
|
type StudentDataPayload,
|
||||||
} from '@/api/classroom_compliance'
|
} from '@/api/classroom_compliance'
|
||||||
|
import ConfirmDialog from '@/components/ConfirmDialog.vue';
|
||||||
import { useAuthStore } from '@/stores/auth'
|
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'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -15,9 +18,15 @@ const props = defineProps<{
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const apiClient = new ClassroomComplianceAPIClient(authStore)
|
||||||
|
|
||||||
const cls: Ref<Class | null> = ref(null)
|
const cls: Ref<Class | null> = ref(null)
|
||||||
const student: Ref<Student | 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 {
|
interface StudentFormData {
|
||||||
name: string
|
name: string
|
||||||
|
@ -85,6 +94,29 @@ function resetForm() {
|
||||||
}
|
}
|
||||||
router.back()
|
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>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-if="cls">
|
<div v-if="cls">
|
||||||
|
@ -114,7 +146,34 @@ function resetForm() {
|
||||||
<div class="button-bar">
|
<div class="button-bar">
|
||||||
<button type="submit">Save</button>
|
<button type="submit">Save</button>
|
||||||
<button type="reset">Cancel</button>
|
<button type="reset">Cancel</button>
|
||||||
|
<button type="button" @click="showMoveClassDialog()">Move to another class</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue