Merge remote-tracking branch 'origin/feat/class-functionality' into feat/class-functionality-fix-bugs
This commit is contained in:
commit
6c408d516c
9 changed files with 293 additions and 213 deletions
|
@ -82,5 +82,7 @@
|
||||||
"reject": "zurückweisen",
|
"reject": "zurückweisen",
|
||||||
"areusure": "Sind Sie sicher?",
|
"areusure": "Sind Sie sicher?",
|
||||||
"yes": "ja",
|
"yes": "ja",
|
||||||
"teachers": "Lehrer"
|
"teachers": "Lehrer",
|
||||||
|
"rejected": "abgelehnt",
|
||||||
|
"accepted": "akzeptiert"
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,5 +82,7 @@
|
||||||
"reject": "reject",
|
"reject": "reject",
|
||||||
"areusure": "Are you sure?",
|
"areusure": "Are you sure?",
|
||||||
"yes": "yes",
|
"yes": "yes",
|
||||||
"teachers": "teachers"
|
"teachers": "teachers",
|
||||||
|
"accepted": "accepted",
|
||||||
|
"rejected": "rejected"
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,5 +82,7 @@
|
||||||
"reject": "rejeter",
|
"reject": "rejeter",
|
||||||
"areusure": "Êtes-vous sûr?",
|
"areusure": "Êtes-vous sûr?",
|
||||||
"yes": "oui",
|
"yes": "oui",
|
||||||
"teachers": "enseignants"
|
"teachers": "enseignants",
|
||||||
|
"accepted": "acceptée",
|
||||||
|
"rejected": "rejetée"
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,5 +82,7 @@
|
||||||
"reject": "weiger",
|
"reject": "weiger",
|
||||||
"areusure": "Bent u zeker?",
|
"areusure": "Bent u zeker?",
|
||||||
"yes": "ja",
|
"yes": "ja",
|
||||||
"teachers": "leerkrachten"
|
"teachers": "leerkrachten",
|
||||||
|
"accepted": "geaccepteerd",
|
||||||
|
"rejected": "geweigerd"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import SingleAssignment from "@/views/assignments/SingleAssignment.vue";
|
||||||
import SingleClass from "@/views/classes/SingleClass.vue";
|
import SingleClass from "@/views/classes/SingleClass.vue";
|
||||||
import SingleDiscussion from "@/views/discussions/SingleDiscussion.vue";
|
import SingleDiscussion from "@/views/discussions/SingleDiscussion.vue";
|
||||||
import NotFound from "@/components/errors/NotFound.vue";
|
import NotFound from "@/components/errors/NotFound.vue";
|
||||||
import CreateClass from "@/views/classes/CreateClass.vue";
|
|
||||||
import CreateAssignment from "@/views/assignments/CreateAssignment.vue";
|
import CreateAssignment from "@/views/assignments/CreateAssignment.vue";
|
||||||
import CreateDiscussion from "@/views/discussions/CreateDiscussion.vue";
|
import CreateDiscussion from "@/views/discussions/CreateDiscussion.vue";
|
||||||
import CallbackPage from "@/views/CallbackPage.vue";
|
import CallbackPage from "@/views/CallbackPage.vue";
|
||||||
|
@ -84,12 +83,6 @@ const router = createRouter({
|
||||||
component: SingleAssignment,
|
component: SingleAssignment,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/class/create",
|
|
||||||
name: "CreateClass",
|
|
||||||
component: CreateClass,
|
|
||||||
meta: { requiresAuth: true },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/class/:id",
|
path: "/class/:id",
|
||||||
name: "SingleClass",
|
name: "SingleClass",
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
<script setup lang="ts"></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main></main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
|
@ -2,72 +2,82 @@
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import authState from "@/services/auth/auth-service.ts";
|
import authState from "@/services/auth/auth-service.ts";
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import type { ClassDTO } from "@dwengo-1/common/interfaces/class";
|
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { ClassController, type ClassResponse } from "@/controllers/classes";
|
import type { ClassResponse } from "@/controllers/classes";
|
||||||
import type { JoinRequestsResponse, StudentsResponse } from "@/controllers/students";
|
import type { JoinRequestsResponse, StudentsResponse } from "@/controllers/students";
|
||||||
import type { StudentDTO } from "@dwengo-1/common/interfaces/student";
|
import type { StudentDTO } from "@dwengo-1/common/interfaces/student";
|
||||||
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
||||||
import { useTeacherJoinRequestsQuery, useUpdateJoinRequestMutation } from "@/queries/teachers";
|
import { useTeacherJoinRequestsQuery, useUpdateJoinRequestMutation } from "@/queries/teachers";
|
||||||
import type { ClassJoinRequestDTO } from "@dwengo-1/common/interfaces/class-join-request";
|
import type { ClassJoinRequestDTO } from "@dwengo-1/common/interfaces/class-join-request";
|
||||||
|
import { useClassDeleteStudentMutation, useClassQuery, useClassStudentsQuery } from "@/queries/classes";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
// Username of logged in teacher
|
|
||||||
const username = ref<string | undefined>(undefined);
|
|
||||||
const classController: ClassController = new ClassController();
|
|
||||||
|
|
||||||
// Find class id from route
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const classId: string = route.params.id as string;
|
const classId: string = route.params.id as string;
|
||||||
|
const username = ref<string | undefined>(undefined);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const isError = ref(false);
|
||||||
|
const errorMessage = ref<string>("");
|
||||||
|
|
||||||
const isLoading = ref(true);
|
// Queries used to access the backend and catch loading or errors
|
||||||
const currentClass = ref<ClassDTO | undefined>(undefined);
|
|
||||||
const students = ref<StudentDTO[]>([]);
|
|
||||||
|
|
||||||
|
// Gets the class a teacher wants to manage
|
||||||
|
const getClass = useClassQuery(classId);
|
||||||
|
// Get all students part of the class
|
||||||
|
const getStudents = useClassStudentsQuery(classId);
|
||||||
|
// Get all join requests for this class
|
||||||
const joinRequestsQuery = useTeacherJoinRequestsQuery(username, classId);
|
const joinRequestsQuery = useTeacherJoinRequestsQuery(username, classId);
|
||||||
|
// Handle accepting or rejecting join requests
|
||||||
const { mutate } = useUpdateJoinRequestMutation();
|
const { mutate } = useUpdateJoinRequestMutation();
|
||||||
|
// Handle deletion of a student from the class
|
||||||
|
const { mutate: deleteStudentMutation } = useClassDeleteStudentMutation();
|
||||||
|
|
||||||
// Find the username of the logged in user so it can be used to fetch other information
|
// Load current user before rendering the page
|
||||||
// When loading the page
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const userObject = await authState.loadUser();
|
isLoading.value = true;
|
||||||
username.value = userObject?.profile?.preferred_username ?? undefined;
|
try {
|
||||||
|
const userObject = await authState.loadUser();
|
||||||
// Get class of which information should be shown
|
username.value = userObject!.profile!.preferred_username;
|
||||||
const classResponse: ClassResponse = await classController.getById(classId);
|
} catch (error) {
|
||||||
if (classResponse && classResponse.class) {
|
isError.value = true;
|
||||||
currentClass.value = classResponse.class;
|
errorMessage.value = error instanceof Error ? error.message : String(error);
|
||||||
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all students of the class
|
|
||||||
const studentsResponse: StudentsResponse = await classController.getStudents(classId);
|
|
||||||
if (studentsResponse && studentsResponse.students) students.value = studentsResponse.students as StudentDTO[];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Boolean that handles visibility for dialogs
|
// Used to set the visibility of the dialog
|
||||||
// Popup to verify removing student
|
|
||||||
const dialog = ref(false);
|
const dialog = ref(false);
|
||||||
|
// Student selected for deletion
|
||||||
const selectedStudent = ref<StudentDTO | null>(null);
|
const selectedStudent = ref<StudentDTO | null>(null);
|
||||||
|
|
||||||
|
// Let the teacher verify deletion of a student
|
||||||
function showPopup(s: StudentDTO): void {
|
function showPopup(s: StudentDTO): void {
|
||||||
selectedStudent.value = s;
|
selectedStudent.value = s;
|
||||||
dialog.value = true;
|
dialog.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove student from class
|
|
||||||
async function removeStudentFromclass(): Promise<void> {
|
async function removeStudentFromclass(): Promise<void> {
|
||||||
// TODO: replace by query
|
// Delete student from class
|
||||||
if (selectedStudent.value) await classController.deleteStudent(classId, selectedStudent.value.username);
|
deleteStudentMutation(
|
||||||
dialog.value = false;
|
{ id: classId, username: selectedStudent.value!.username },
|
||||||
|
{
|
||||||
selectedStudent.value = null;
|
onSuccess: async () => {
|
||||||
//TODO when query; reload table so student not longer in table
|
dialog.value = false;
|
||||||
|
await getStudents.refetch();
|
||||||
|
showSnackbar(t("success"), "success");
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
dialog.value = false;
|
||||||
|
showSnackbar(t("failed") + ": " + e.message, "error");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: query + relaoding
|
function handleJoinRequest(c: ClassJoinRequestDTO, accepted: boolean): void {
|
||||||
function handleJoinRequest(c: ClassJoinRequestDTO, accepted: boolean) : void {
|
// Handle acception or rejection of a join request
|
||||||
mutate(
|
mutate(
|
||||||
{
|
{
|
||||||
teacherUsername: username.value!,
|
teacherUsername: username.value!,
|
||||||
|
@ -76,22 +86,32 @@
|
||||||
accepted: accepted,
|
accepted: accepted,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
showSnackbar(t("sent"), "success");
|
if (accepted) {
|
||||||
|
await joinRequestsQuery.refetch();
|
||||||
|
await getStudents.refetch();
|
||||||
|
|
||||||
|
showSnackbar(t("accepted"), "success");
|
||||||
|
} else {
|
||||||
|
await joinRequestsQuery.refetch();
|
||||||
|
showSnackbar(t("rejected"), "success");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
// ShowSnackbar(t("failed") + ": " + e.message, "error");
|
showSnackbar(t("failed") + ": " + e.message, "error");
|
||||||
throw e;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default of snackbar values
|
||||||
const snackbar = ref({
|
const snackbar = ref({
|
||||||
visible: false,
|
visible: false,
|
||||||
message: "",
|
message: "",
|
||||||
color: "success",
|
color: "success",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Function to show snackbar on success or failure
|
||||||
function showSnackbar(message: string, color: string): void {
|
function showSnackbar(message: string, color: string): void {
|
||||||
snackbar.value.message = message;
|
snackbar.value.message = message;
|
||||||
snackbar.value.color = color;
|
snackbar.value.color = color;
|
||||||
|
@ -101,131 +121,144 @@
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div
|
<div
|
||||||
|
class="loading-div"
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
class="text-center py-10"
|
|
||||||
>
|
>
|
||||||
<v-progress-circular
|
<v-progress-circular indeterminate></v-progress-circular>
|
||||||
indeterminate
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
<p>Loading...</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-if="isError">
|
||||||
<h1 class="title">{{ currentClass!.displayName }}</h1>
|
<v-empty-state
|
||||||
<v-container
|
icon="mdi-alert-circle-outline"
|
||||||
fluid
|
:text="errorMessage"
|
||||||
class="ma-4"
|
:title="t('error_title')"
|
||||||
>
|
></v-empty-state>
|
||||||
<v-row
|
</div>
|
||||||
no-gutters
|
<using-query-result
|
||||||
fluid
|
:query-result="getClass"
|
||||||
|
v-slot="classResponse: { data: ClassResponse }"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h1 class="title">{{ classResponse.data.class.displayName }}</h1>
|
||||||
|
<using-query-result
|
||||||
|
:query-result="getStudents"
|
||||||
|
v-slot="studentsResponse: { data: StudentsResponse }"
|
||||||
>
|
>
|
||||||
<v-col
|
<v-container
|
||||||
cols="12"
|
fluid
|
||||||
sm="6"
|
class="ma-4"
|
||||||
md="6"
|
|
||||||
>
|
>
|
||||||
<v-table class="table">
|
<v-row
|
||||||
<thead>
|
no-gutters
|
||||||
<tr>
|
fluid
|
||||||
<th class="header">{{ t("students") }}</th>
|
|
||||||
<th class="header"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="s in students"
|
|
||||||
:key="s.id"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
{{ s.firstName + " " + s.lastName }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<v-btn @click="showPopup(s)"> {{ t("remove") }} </v-btn>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</v-table>
|
|
||||||
</v-col>
|
|
||||||
<using-query-result
|
|
||||||
:query-result="joinRequestsQuery"
|
|
||||||
v-slot="joinRequests: { data: JoinRequestsResponse }"
|
|
||||||
>
|
|
||||||
<v-col
|
|
||||||
cols="12"
|
|
||||||
sm="6"
|
|
||||||
md="6"
|
|
||||||
>
|
>
|
||||||
<v-table class="table">
|
<v-col
|
||||||
<thead>
|
cols="12"
|
||||||
<tr>
|
sm="6"
|
||||||
<th class="header">{{ t("classJoinRequests") }}</th>
|
md="6"
|
||||||
<th class="header">{{ t("accept") + "/" + t("reject") }}</th>
|
>
|
||||||
</tr>
|
<v-table class="table">
|
||||||
</thead>
|
<thead>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr
|
<th class="header">{{ t("students") }}</th>
|
||||||
v-for="jr in joinRequests.data.joinRequests as ClassJoinRequestDTO[]"
|
<th class="header"></th>
|
||||||
:key="(jr.class, jr.requester, jr.status)"
|
</tr>
|
||||||
>
|
</thead>
|
||||||
<td>
|
<tbody>
|
||||||
{{ jr.requester.firstName + " " + jr.requester.lastName }}
|
<tr
|
||||||
</td>
|
v-for="s in studentsResponse.data.students as StudentDTO[]"
|
||||||
<td>
|
:key="s.id"
|
||||||
<v-btn
|
>
|
||||||
@click="handleJoinRequest(jr, true)"
|
<td>
|
||||||
class="mr-2"
|
{{ s.firstName + " " + s.lastName }}
|
||||||
color="green"
|
</td>
|
||||||
>
|
<td>
|
||||||
{{ t("accept") }}</v-btn
|
<v-btn @click="showPopup(s)"> {{ t("remove") }} </v-btn>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</v-table>
|
||||||
|
</v-col>
|
||||||
|
<using-query-result
|
||||||
|
:query-result="joinRequestsQuery"
|
||||||
|
v-slot="joinRequests: { data: JoinRequestsResponse }"
|
||||||
|
>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<v-table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="header">{{ t("classJoinRequests") }}</th>
|
||||||
|
<th class="header">{{ t("accept") + "/" + t("reject") }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="jr in joinRequests.data.joinRequests as ClassJoinRequestDTO[]"
|
||||||
|
:key="(jr.class, jr.requester, jr.status)"
|
||||||
>
|
>
|
||||||
|
<td>
|
||||||
|
{{ jr.requester.firstName + " " + jr.requester.lastName }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<v-btn
|
||||||
|
@click="handleJoinRequest(jr, true)"
|
||||||
|
class="mr-2"
|
||||||
|
color="green"
|
||||||
|
>
|
||||||
|
{{ t("accept") }}</v-btn
|
||||||
|
>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
@click="handleJoinRequest(jr, false)"
|
@click="handleJoinRequest(jr, false)"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
color="red"
|
color="red"
|
||||||
>
|
>
|
||||||
{{ t("reject") }}
|
{{ t("reject") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
</v-col>
|
</v-col>
|
||||||
</using-query-result>
|
</using-query-result>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
</div>
|
</using-query-result>
|
||||||
<v-dialog
|
</div>
|
||||||
v-model="dialog"
|
<v-dialog
|
||||||
max-width="400px"
|
v-model="dialog"
|
||||||
>
|
max-width="400px"
|
||||||
<v-card>
|
>
|
||||||
<v-card-title class="headline">{{ t("areusure") }}</v-card-title>
|
<v-card>
|
||||||
|
<v-card-title class="headline">{{ t("areusure") }}</v-card-title>
|
||||||
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn
|
<v-btn
|
||||||
text
|
text
|
||||||
@click="dialog = false"
|
@click="dialog = false"
|
||||||
>
|
>
|
||||||
{{ t("cancel") }}
|
{{ t("cancel") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
text
|
text
|
||||||
@click="removeStudentFromclass"
|
@click="removeStudentFromclass"
|
||||||
>{{ t("yes") }}</v-btn
|
>{{ t("yes") }}</v-btn
|
||||||
>
|
>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
<v-snackbar
|
<v-snackbar
|
||||||
v-model="snackbar.visible"
|
v-model="snackbar.visible"
|
||||||
:color="snackbar.color"
|
:color="snackbar.color"
|
||||||
timeout="3000"
|
timeout="3000"
|
||||||
>
|
>
|
||||||
{{ snackbar.message }}
|
{{ snackbar.message }}
|
||||||
</v-snackbar>
|
</v-snackbar>
|
||||||
|
</using-query-result>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import { useCreateJoinRequestMutation, useStudentClassesQuery } from "@/queries/students";
|
import { useCreateJoinRequestMutation, useStudentClassesQuery } from "@/queries/students";
|
||||||
import type { StudentDTO } from "@dwengo-1/common/interfaces/student";
|
import type { StudentDTO } from "@dwengo-1/common/interfaces/student";
|
||||||
import type { TeacherDTO } from "@dwengo-1/common/interfaces/teacher";
|
import type { TeacherDTO } from "@dwengo-1/common/interfaces/teacher";
|
||||||
import { type ClassesResponse } from "@/controllers/classes";
|
import type { ClassesResponse } from "@/controllers/classes";
|
||||||
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
||||||
import { useClassStudentsQuery, useClassTeachersQuery } from "@/queries/classes";
|
import { useClassStudentsQuery, useClassTeachersQuery } from "@/queries/classes";
|
||||||
import type { StudentsResponse } from "@/controllers/students";
|
import type { StudentsResponse } from "@/controllers/students";
|
||||||
|
@ -17,16 +17,26 @@
|
||||||
|
|
||||||
// Username of logged in student
|
// Username of logged in student
|
||||||
const username = ref<string | undefined>(undefined);
|
const username = ref<string | undefined>(undefined);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const isError = ref(false);
|
||||||
|
const errorMessage = ref<string>("");
|
||||||
|
|
||||||
// Students of selected class are shown when logged in student presses on the member count
|
// Students of selected class are shown when logged in student presses on the member count
|
||||||
const selectedClass = ref<ClassDTO | null>(null);
|
const selectedClass = ref<ClassDTO | null>(null);
|
||||||
const getStudents = ref(false);
|
const getStudents = ref(false);
|
||||||
|
|
||||||
// Find the username of the logged in user so it can be used to fetch other information
|
// Load current user before rendering the page
|
||||||
// When loading the page
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const userObject = await authState.loadUser();
|
isLoading.value = true;
|
||||||
username.value = userObject?.profile?.preferred_username ?? undefined;
|
try {
|
||||||
|
const userObject = await authState.loadUser();
|
||||||
|
username.value = userObject!.profile!.preferred_username;
|
||||||
|
} catch (error) {
|
||||||
|
isError.value = true;
|
||||||
|
errorMessage.value = error instanceof Error ? error.message : String(error);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch all classes of the logged in student
|
// Fetch all classes of the logged in student
|
||||||
|
@ -44,7 +54,7 @@
|
||||||
async function openStudentDialog(c: ClassDTO): Promise<void> {
|
async function openStudentDialog(c: ClassDTO): Promise<void> {
|
||||||
selectedClass.value = c;
|
selectedClass.value = c;
|
||||||
|
|
||||||
// let the component know it should show the students in a class
|
// Let the component know it should show the students in a class
|
||||||
getStudents.value = true;
|
getStudents.value = true;
|
||||||
await getStudentsQuery.refetch();
|
await getStudentsQuery.refetch();
|
||||||
dialog.value = true;
|
dialog.value = true;
|
||||||
|
@ -53,7 +63,7 @@
|
||||||
async function openTeacherDialog(c: ClassDTO): Promise<void> {
|
async function openTeacherDialog(c: ClassDTO): Promise<void> {
|
||||||
selectedClass.value = c;
|
selectedClass.value = c;
|
||||||
|
|
||||||
// let the component know it should show teachers of a class
|
// Let the component know it should show teachers of a class
|
||||||
getStudents.value = false;
|
getStudents.value = false;
|
||||||
await getTeachersQuery.refetch();
|
await getTeachersQuery.refetch();
|
||||||
dialog.value = true;
|
dialog.value = true;
|
||||||
|
@ -111,7 +121,20 @@
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div>
|
<div
|
||||||
|
class="loading-div"
|
||||||
|
v-if="isLoading"
|
||||||
|
>
|
||||||
|
<v-progress-circular indeterminate></v-progress-circular>
|
||||||
|
</div>
|
||||||
|
<div v-if="isError">
|
||||||
|
<v-empty-state
|
||||||
|
icon="mdi-alert-circle-outline"
|
||||||
|
:text="errorMessage"
|
||||||
|
:title="t('error_title')"
|
||||||
|
></v-empty-state>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
<h1 class="title">{{ t("classes") }}</h1>
|
<h1 class="title">{{ t("classes") }}</h1>
|
||||||
<using-query-result
|
<using-query-result
|
||||||
:query-result="classesQuery"
|
:query-result="classesQuery"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import type { ClassDTO } from "@dwengo-1/common/interfaces/class";
|
import type { ClassDTO } from "@dwengo-1/common/interfaces/class";
|
||||||
import type { TeacherInvitationDTO } from "@dwengo-1/common/interfaces/teacher-invitation";
|
import type { TeacherInvitationDTO } from "@dwengo-1/common/interfaces/teacher-invitation";
|
||||||
import { useTeacherClassesQuery } from "@/queries/teachers";
|
import { useTeacherClassesQuery } from "@/queries/teachers";
|
||||||
import { type ClassesResponse, type ClassResponse } from "@/controllers/classes";
|
import type { ClassesResponse } from "@/controllers/classes";
|
||||||
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
||||||
import { useClassesQuery, useClassTeacherInvitationsQuery, useCreateClassMutation } from "@/queries/classes";
|
import { useClassesQuery, useClassTeacherInvitationsQuery, useCreateClassMutation } from "@/queries/classes";
|
||||||
import type { TeacherInvitationsResponse } from "@/controllers/teacher-invitations";
|
import type { TeacherInvitationsResponse } from "@/controllers/teacher-invitations";
|
||||||
|
@ -15,19 +15,29 @@
|
||||||
|
|
||||||
// Username of logged in teacher
|
// Username of logged in teacher
|
||||||
const username = ref<string | undefined>(undefined);
|
const username = ref<string | undefined>(undefined);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const isError = ref(false);
|
||||||
|
const errorMessage = ref<string>("");
|
||||||
|
|
||||||
// Find the username of the logged in user so it can be used to fetch other information
|
// Load current user before rendering the page
|
||||||
// When loading the page
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const userObject = await authState.loadUser();
|
isLoading.value = true;
|
||||||
username.value = userObject?.profile?.preferred_username ?? undefined;
|
try {
|
||||||
|
const userObject = await authState.loadUser();
|
||||||
|
username.value = userObject!.profile!.preferred_username;
|
||||||
|
} catch (error) {
|
||||||
|
isError.value = true;
|
||||||
|
errorMessage.value = error instanceof Error ? error.message : String(error);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch all classes of the logged in teacher
|
// Fetch all classes of the logged in teacher
|
||||||
const classesQuery = useTeacherClassesQuery(username, true);
|
const classesQuery = useTeacherClassesQuery(username, true);
|
||||||
const allClassesQuery = useClassesQuery();
|
const allClassesQuery = useClassesQuery();
|
||||||
const { mutate } = useCreateClassMutation();
|
const { mutate } = useCreateClassMutation();
|
||||||
const getInvitationsQuery = useClassTeacherInvitationsQuery(username);
|
const getInvitationsQuery = useClassTeacherInvitationsQuery(username); // TODO: use useTeacherInvitationsReceivedQuery
|
||||||
|
|
||||||
// Boolean that handles visibility for dialogs
|
// Boolean that handles visibility for dialogs
|
||||||
// Creating a class will generate a popup with the generated code
|
// Creating a class will generate a popup with the generated code
|
||||||
|
@ -111,6 +121,19 @@
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
|
<div
|
||||||
|
class="loading-div"
|
||||||
|
v-if="isLoading"
|
||||||
|
>
|
||||||
|
<v-progress-circular indeterminate></v-progress-circular>
|
||||||
|
</div>
|
||||||
|
<div v-if="isError">
|
||||||
|
<v-empty-state
|
||||||
|
icon="mdi-alert-circle-outline"
|
||||||
|
:text="errorMessage"
|
||||||
|
:title="t('error_title')"
|
||||||
|
></v-empty-state>
|
||||||
|
</div v-else>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="title">{{ t("classes") }}</h1>
|
<h1 class="title">{{ t("classes") }}</h1>
|
||||||
<using-query-result
|
<using-query-result
|
||||||
|
@ -252,34 +275,41 @@
|
||||||
:query-result="getInvitationsQuery"
|
:query-result="getInvitationsQuery"
|
||||||
v-slot="invitationsResponse: { data: TeacherInvitationsResponse }"
|
v-slot="invitationsResponse: { data: TeacherInvitationsResponse }"
|
||||||
>
|
>
|
||||||
<using-query-result :query-result="allClassesQuery" v-slot="classesResponse: {data: ClassesResponse}">
|
<using-query-result
|
||||||
<tr
|
:query-result="allClassesQuery"
|
||||||
v-for="i in invitationsResponse.data.invitations as TeacherInvitationDTO[]"
|
v-slot="classesResponse: { data: ClassesResponse }"
|
||||||
:key="i.classId"
|
|
||||||
>
|
>
|
||||||
<td>
|
<tr
|
||||||
{{ (classesResponse.data.classes as ClassDTO[]).filter((c) => c.id == i.classId)[0] }}
|
v-for="i in invitationsResponse.data.invitations as TeacherInvitationDTO[]"
|
||||||
</td>
|
:key="i.classId"
|
||||||
<td>{{ (i.sender as TeacherDTO).firstName + " " + (i.sender as TeacherDTO).lastName }}</td>
|
>
|
||||||
<td class="text-right">
|
<td>
|
||||||
<div>
|
{{
|
||||||
<v-btn
|
(classesResponse.data.classes as ClassDTO[]).filter((c) => c.id == i.classId)[0]
|
||||||
color="green"
|
}}
|
||||||
@click="acceptRequest"
|
</td>
|
||||||
class="mr-2"
|
<td>
|
||||||
>
|
{{ (i.sender as TeacherDTO).firstName + " " + (i.sender as TeacherDTO).lastName }}
|
||||||
{{ t("accept") }}
|
</td>
|
||||||
</v-btn>
|
<td class="text-right">
|
||||||
<v-btn
|
<div>
|
||||||
color="red"
|
<v-btn
|
||||||
@click="denyRequest"
|
color="green"
|
||||||
>
|
@click="acceptRequest"
|
||||||
{{ t("deny") }}
|
class="mr-2"
|
||||||
</v-btn>
|
>
|
||||||
</div>
|
{{ t("accept") }}
|
||||||
</td>
|
</v-btn>
|
||||||
</tr>
|
<v-btn
|
||||||
</using-query-result>
|
color="red"
|
||||||
|
@click="denyRequest"
|
||||||
|
>
|
||||||
|
{{ t("deny") }}
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</using-query-result>
|
||||||
</using-query-result>
|
</using-query-result>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue