feat(frontend): eerste queries voor assignments
This commit is contained in:
parent
ce5e9cd342
commit
45cb020861
10 changed files with 560 additions and 218 deletions
44
frontend/src/assets/assignment.css
Normal file
44
frontend/src/assets/assignment.css
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2%;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assignment-card {
|
||||||
|
width: 85%;
|
||||||
|
padding: 2%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-top: 2%;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-right-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 2%;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-section {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-section h3 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-section ul {
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
|
@ -13,15 +13,10 @@ import type {LearningPath} from "@/data-objects/learning-paths/learning-path.ts"
|
||||||
import type {ClassesResponse} from "@/controllers/classes.ts";
|
import type {ClassesResponse} from "@/controllers/classes.ts";
|
||||||
import type {AssignmentDTO} from "@dwengo-1/common/interfaces/assignment";
|
import type {AssignmentDTO} from "@dwengo-1/common/interfaces/assignment";
|
||||||
import {AssignmentController} from "@/controllers/assignments.ts";
|
import {AssignmentController} from "@/controllers/assignments.ts";
|
||||||
import type {GroupDTO} from "@dwengo-1/common/interfaces/group";
|
|
||||||
import {GroupController} from "@/controllers/groups.ts";
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
TODO: when clicking the assign button from lp page pass the lp-object in a state:
|
TODO: when clicking the assign button from lp page pass the lp-object in a state:
|
||||||
*/
|
*/
|
||||||
const props = defineProps<{
|
|
||||||
learningPath?: LearningPath | null; // Optional learningPath prop
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {t, locale} = useI18n();
|
const {t, locale} = useI18n();
|
||||||
|
@ -48,26 +43,7 @@ async function submitForm(assignmentTitle: string,
|
||||||
|
|
||||||
//TODO: replace with query function
|
//TODO: replace with query function
|
||||||
const controller: AssignmentController = new AssignmentController(selectedClass);
|
const controller: AssignmentController = new AssignmentController(selectedClass);
|
||||||
const response = await controller.createAssignment(assignmentDTO);
|
await controller.createAssignment(assignmentDTO);
|
||||||
// Create groups
|
|
||||||
for (let i = 0; i < groups.length; i++) {
|
|
||||||
const group: GroupDTO = {
|
|
||||||
assignment: response.id,
|
|
||||||
groupNumber: i,
|
|
||||||
members: groups[i]
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("Posting group:", group);
|
|
||||||
|
|
||||||
const groupController: GroupController = new GroupController(selectedClass, response.id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await groupController.createGroup(group);
|
|
||||||
console.log("Group successfully posted:", group);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Group POST failed:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await router.push('/user/assignment');
|
await router.push('/user/assignment');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, defineProps, defineEmits} from 'vue';
|
import {ref, defineProps, defineEmits} from 'vue';
|
||||||
import {useI18n} from 'vue-i18n';
|
import {useI18n} from 'vue-i18n';
|
||||||
import {useClassStudentsQuery} from "@/queries/classes.ts";
|
import {useClassStudentsQuery} from "@/queries/classes-temp.ts";
|
||||||
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
||||||
import type {StudentsResponse} from "@/controllers/students.ts";
|
import type {StudentsResponse} from "@/controllers/students.ts";
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,55 @@
|
||||||
import {useMutation, useQueryClient, type UseMutationReturnType} from "@tanstack/vue-query";
|
import {computed, type MaybeRefOrGetter, toValue} from "vue";
|
||||||
import {AssignmentController, type AssignmentResponse} from "@/controllers/assignments.ts";
|
import {useQuery, type UseQueryReturnType} from "@tanstack/vue-query";
|
||||||
import type {AssignmentDTO} from "@dwengo-1/common/interfaces/assignment";
|
import {AssignmentController, type AssignmentResponse, type AssignmentsResponse} from "@/controllers/assignments.ts";
|
||||||
import {toValue} from "vue";
|
import type {SubmissionsResponse} from "@/controllers/submissions.ts";
|
||||||
|
|
||||||
export function useCreateAssignmentMutation(classId: string): UseMutationReturnType<AssignmentResponse, Error, AssignmentDTO, unknown> {
|
export function useAssignmentsQuery(classId: MaybeRefOrGetter<string | undefined>, full: MaybeRefOrGetter<boolean> = true): UseQueryReturnType<AssignmentsResponse, Error> {
|
||||||
const queryClient = useQueryClient();
|
const resolvedClassId = toValue(classId) as string;
|
||||||
|
const resolvedFull = toValue(full);
|
||||||
|
|
||||||
const assignmentController = new AssignmentController(toValue(classId));
|
const assignmentController = new AssignmentController(resolvedClassId);
|
||||||
|
return useQuery({
|
||||||
return useMutation({
|
queryKey: computed(() => [
|
||||||
mutationFn: async (data) => assignmentController.createAssignment(data),
|
'assignments',
|
||||||
onSuccess: async () => {
|
resolvedClassId,
|
||||||
await queryClient.invalidateQueries({queryKey: ["assignments"]});
|
resolvedFull,
|
||||||
},
|
]),
|
||||||
});
|
queryFn: async () => assignmentController.getAll(resolvedFull),
|
||||||
|
enabled: () => Boolean(resolvedClassId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function useAssignmentQuery(classId: MaybeRefOrGetter<string | undefined>, num: MaybeRefOrGetter<number>): UseQueryReturnType<AssignmentResponse, Error> {
|
||||||
|
const resolvedClassId = toValue(classId) as string;
|
||||||
|
const resolvedNum = toValue(num);
|
||||||
|
|
||||||
|
const assignmentController = new AssignmentController(resolvedClassId);
|
||||||
|
return useQuery({
|
||||||
|
queryKey: computed(() => [
|
||||||
|
'assignment',
|
||||||
|
resolvedClassId,
|
||||||
|
resolvedNum,
|
||||||
|
]),
|
||||||
|
queryFn: async () => assignmentController.getByNumber(resolvedNum),
|
||||||
|
enabled: () => Boolean(resolvedClassId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSubmissionsQuery(classId: MaybeRefOrGetter<string | undefined>, assignmentNum: MaybeRefOrGetter<number>, full: MaybeRefOrGetter<boolean>): UseQueryReturnType<SubmissionsResponse, Error> {
|
||||||
|
const resolvedClassId = toValue(classId) as string;
|
||||||
|
const resolvedNum = toValue(assignmentNum);
|
||||||
|
const resolvedFull = toValue(full);
|
||||||
|
|
||||||
|
const assignmentController = new AssignmentController(resolvedClassId);
|
||||||
|
return useQuery({
|
||||||
|
queryKey: computed(() => [
|
||||||
|
'submissions',
|
||||||
|
resolvedClassId,
|
||||||
|
resolvedNum,
|
||||||
|
resolvedFull
|
||||||
|
]),
|
||||||
|
queryFn: async () => assignmentController.getSubmissions(resolvedNum, resolvedFull),
|
||||||
|
enabled: () => Boolean(resolvedClassId)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {useQuery, type UseQueryReturnType} from "@tanstack/vue-query";
|
|
||||||
import {computed, type MaybeRefOrGetter, toValue} from "vue";
|
|
||||||
import type {StudentsResponse} from "@/controllers/students.ts";
|
|
||||||
import {getClassController} from "@/controllers/controllers.ts";
|
import {getClassController} from "@/controllers/controllers.ts";
|
||||||
|
import {computed, type MaybeRefOrGetter, toValue} from "vue";
|
||||||
|
import {useQuery, type UseQueryReturnType} from "@tanstack/vue-query";
|
||||||
|
import type {StudentsResponse} from "@/controllers/students.ts";
|
||||||
|
|
||||||
const classController = getClassController();
|
const classController = getClassController();
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ function classStudentsQueryKey(classId: string, full: boolean): [string, string,
|
||||||
return ["class-students", classId, full];
|
return ["class-students", classId, full];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: delete and use the one in classes.ts
|
||||||
export function useClassStudentsQuery(
|
export function useClassStudentsQuery(
|
||||||
classId: MaybeRefOrGetter<string | undefined>,
|
classId: MaybeRefOrGetter<string | undefined>,
|
||||||
full: MaybeRefOrGetter<boolean> = true,
|
full: MaybeRefOrGetter<boolean> = true,
|
200
frontend/src/views/assignments/Assignment.vue
Normal file
200
frontend/src/views/assignments/Assignment.vue
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import {ref, computed} from "vue";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
import auth from "@/services/auth/auth-service.ts";
|
||||||
|
import {AssignmentController} from "@/controllers/assignments.ts";
|
||||||
|
import {asyncComputed} from "@vueuse/core";
|
||||||
|
|
||||||
|
|
||||||
|
const {t, locale} = useI18n();
|
||||||
|
const language = computed(() => locale.value);
|
||||||
|
const route = useRoute();
|
||||||
|
const assignmentId = ref(Number(route.params.id));
|
||||||
|
const classId = window.history.state?.class_id;
|
||||||
|
const controller = new AssignmentController(classId);
|
||||||
|
|
||||||
|
const role = auth.authState.activeRole;
|
||||||
|
const isTeacher = computed(() => role === 'teacher');
|
||||||
|
|
||||||
|
const assignment = asyncComputed(async () => {
|
||||||
|
return await controller.getByNumber(assignmentId.value)
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
|
||||||
|
const submitted = ref(true);//TODO: update by fetching submissions and check group
|
||||||
|
|
||||||
|
const submitAssignment = async () => {
|
||||||
|
//TODO
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/***
|
||||||
|
// Display group members
|
||||||
|
const myGroup = computed(() => {
|
||||||
|
if (!assignment.value || !assignment.value.groups) return null;
|
||||||
|
console.log(assignment.value.groups)
|
||||||
|
return assignment.value.groups.find(group =>
|
||||||
|
group.members.some(m => m.username === myUsername)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
const deleteAssignment = async () => {
|
||||||
|
await controller.deleteAssignment(assignmentId.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<v-card v-if="assignment" class="assignment-card">
|
||||||
|
<div class="top-buttons">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
variant="text"
|
||||||
|
class="back-btn"
|
||||||
|
to="/user/assignment"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-arrow-left</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-if="isTeacher"
|
||||||
|
icon
|
||||||
|
variant="text"
|
||||||
|
class="top-right-btn"
|
||||||
|
@click="deleteAssignment"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-chip
|
||||||
|
v-if="!isTeacher"
|
||||||
|
class="ma-2 top-right-btn"
|
||||||
|
label
|
||||||
|
color="success"
|
||||||
|
>
|
||||||
|
{{ t("submitted") }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
<v-card-title class="text-h4">{{ assignment.title }}</v-card-title>
|
||||||
|
<v-card-subtitle class="subtitle-section">
|
||||||
|
<v-btn
|
||||||
|
:to="`/learningPath/${language}/${assignment.learningPath}`"
|
||||||
|
variant="tonal"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{{ t("learning-path") }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-subtitle>
|
||||||
|
|
||||||
|
<v-card-text class="description">
|
||||||
|
{{ assignment.description }}
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-text class="group-section">
|
||||||
|
<h3>{{ t("group") }}</h3>
|
||||||
|
|
||||||
|
<!-- Student view
|
||||||
|
<div v-if="!isTeacher">
|
||||||
|
<div v-if="myGroup">
|
||||||
|
<ul>
|
||||||
|
<li v-for="student in myGroup.members" :key="student.username">
|
||||||
|
{{ student.firstName + ' ' + student.lastName}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>-->
|
||||||
|
|
||||||
|
<!-- Teacher view
|
||||||
|
<div v-if="isTeacher">
|
||||||
|
<v-expansion-panels>
|
||||||
|
<v-expansion-panel
|
||||||
|
v-for="(group, index) in assignment.groups"
|
||||||
|
:key="group.id"
|
||||||
|
>
|
||||||
|
<v-expansion-panel-title>
|
||||||
|
{{ t("group") }} {{ index + 1 }}
|
||||||
|
</v-expansion-panel-title>
|
||||||
|
<v-expansion-panel-text>
|
||||||
|
<ul>
|
||||||
|
<li v-for="student in group.members" :key="student.username">
|
||||||
|
{{ student.firstName + ' ' + student.lastName }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</v-expansion-panel-text>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
|
</div>-->
|
||||||
|
</v-card-text >
|
||||||
|
<v-card-actions class="justify-end">
|
||||||
|
<v-btn
|
||||||
|
v-if="!isTeacher"
|
||||||
|
size="large"
|
||||||
|
color="success"
|
||||||
|
variant="flat"
|
||||||
|
@click="submitAssignment"
|
||||||
|
>
|
||||||
|
{{ t("submit") }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
v-if="isTeacher"
|
||||||
|
size="large"
|
||||||
|
color="success"
|
||||||
|
variant="text"
|
||||||
|
>
|
||||||
|
{{ t("view-submissions") }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2%;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assignment-card {
|
||||||
|
width: 85%;
|
||||||
|
padding: 2%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-top: 2%;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-right-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 2%;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-section {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-section h3 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-section ul {
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,162 +1,20 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import {ref, computed} from "vue";
|
|
||||||
import {useI18n} from "vue-i18n";
|
|
||||||
import auth from "@/services/auth/auth-service.ts";
|
|
||||||
import {AssignmentController} from "@/controllers/assignments.ts";
|
|
||||||
import {asyncComputed} from "@vueuse/core";
|
|
||||||
|
|
||||||
|
import auth from "@/services/auth/auth-service.ts";
|
||||||
|
import {computed} from "vue";
|
||||||
|
import StudentAssignment from "@/views/assignments/StudentAssignment.vue";
|
||||||
|
import TeacherAssignment from "@/views/assignments/TeacherAssignment.vue";
|
||||||
|
|
||||||
const {t, locale} = useI18n();
|
const role = auth.authState.activeRole;
|
||||||
const language = computed(() => locale.value);
|
const isTeacher = computed(() => role === 'teacher');
|
||||||
const route = useRoute();
|
|
||||||
const assignmentId = ref(Number(route.params.id));
|
|
||||||
const classId = window.history.state?.class_id;
|
|
||||||
const controller = new AssignmentController(classId);
|
|
||||||
|
|
||||||
const role = auth.authState.activeRole;
|
|
||||||
const isTeacher = computed(() => role === 'teacher');
|
|
||||||
|
|
||||||
const assignment = asyncComputed(async () => {
|
|
||||||
return await controller.getByNumber(assignmentId.value)
|
|
||||||
}, null);
|
|
||||||
|
|
||||||
|
|
||||||
/***
|
|
||||||
// Display group members
|
|
||||||
const myGroup = computed(() => {
|
|
||||||
if (!assignment.value || !assignment.value.groups) return null;
|
|
||||||
console.log(assignment.value.groups)
|
|
||||||
return assignment.value.groups.find(group =>
|
|
||||||
group.members.some(m => m.username === myUsername)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
const deleteAssignment = async () => {
|
|
||||||
await controller.deleteAssignment(assignmentId.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container">
|
<StudentAssignment v-if="!isTeacher"></StudentAssignment>
|
||||||
<v-card v-if="assignment" class="assignment-card">
|
<TeacherAssignment v-else></TeacherAssignment>
|
||||||
<div class="top-buttons">
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
variant="text"
|
|
||||||
class="back-btn"
|
|
||||||
to="/user/assignment"
|
|
||||||
>
|
|
||||||
<v-icon>mdi-arrow-left</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<v-btn
|
|
||||||
v-if="isTeacher"
|
|
||||||
icon
|
|
||||||
variant="text"
|
|
||||||
class="delete-btn"
|
|
||||||
@click="deleteAssignment"
|
|
||||||
>
|
|
||||||
<v-icon>mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
<v-card-title class="text-h4">{{ assignment.title }}</v-card-title>
|
|
||||||
<v-card-subtitle>
|
|
||||||
<v-btn
|
|
||||||
:to="`/learningPath/${language}/${assignment.learningPath}`"
|
|
||||||
variant="tonal"
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
{{ t("learning-path") }}
|
|
||||||
</v-btn>
|
|
||||||
</v-card-subtitle>
|
|
||||||
|
|
||||||
<v-card-text class="description">
|
|
||||||
{{ assignment.description }}
|
|
||||||
</v-card-text>
|
|
||||||
|
|
||||||
<v-card-text class="group-section">
|
|
||||||
<h3>{{ t("group") }}</h3>
|
|
||||||
|
|
||||||
<!-- Student view
|
|
||||||
<div v-if="!isTeacher">
|
|
||||||
<div v-if="myGroup">
|
|
||||||
<ul>
|
|
||||||
<li v-for="student in myGroup.members" :key="student.username">
|
|
||||||
{{ student.firstName + ' ' + student.lastName}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>-->
|
|
||||||
|
|
||||||
<!-- Teacher view
|
|
||||||
<div v-if="isTeacher">
|
|
||||||
<v-expansion-panels>
|
|
||||||
<v-expansion-panel
|
|
||||||
v-for="(group, index) in assignment.groups"
|
|
||||||
:key="group.id"
|
|
||||||
>
|
|
||||||
<v-expansion-panel-title>
|
|
||||||
{{ t("group") }} {{ index + 1 }}
|
|
||||||
</v-expansion-panel-title>
|
|
||||||
<v-expansion-panel-text>
|
|
||||||
<ul>
|
|
||||||
<li v-for="student in group.members" :key="student.username">
|
|
||||||
{{ student.firstName + ' ' + student.lastName }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</v-expansion-panel-text>
|
|
||||||
</v-expansion-panel>
|
|
||||||
</v-expansion-panels>
|
|
||||||
</div>-->
|
|
||||||
</v-card-text>
|
|
||||||
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 2%;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assignment-card {
|
|
||||||
width: 85%;
|
|
||||||
padding: 2%;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
margin-top: 2%;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-btn {
|
|
||||||
position: absolute;
|
|
||||||
right: 1%;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-section {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-section h3 {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-section ul {
|
|
||||||
padding-left: 1.2rem;
|
|
||||||
list-style-type: disc;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
110
frontend/src/views/assignments/StudentAssignment.vue
Normal file
110
frontend/src/views/assignments/StudentAssignment.vue
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import {ref, computed} from "vue";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
import {AssignmentController} from "@/controllers/assignments.ts";
|
||||||
|
import {asyncComputed} from "@vueuse/core";
|
||||||
|
|
||||||
|
|
||||||
|
const {t, locale} = useI18n();
|
||||||
|
const language = computed(() => locale.value);
|
||||||
|
const route = useRoute();
|
||||||
|
const assignmentId = ref(Number(route.params.id));
|
||||||
|
const classId = window.history.state?.class_id;
|
||||||
|
const controller = new AssignmentController(classId);
|
||||||
|
|
||||||
|
const assignment = asyncComputed(async () => {
|
||||||
|
return await controller.getByNumber(assignmentId.value)
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
const submitted = ref(true);//TODO: update by fetching submissions and check group
|
||||||
|
|
||||||
|
const submitAssignment = async () => {
|
||||||
|
//TODO
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/***
|
||||||
|
// Display group members
|
||||||
|
const myGroup = computed(() => {
|
||||||
|
if (!assignment.value || !assignment.value.groups) return null;
|
||||||
|
console.log(assignment.value.groups)
|
||||||
|
return assignment.value.groups.find(group =>
|
||||||
|
group.members.some(m => m.username === myUsername)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<v-card v-if="assignment" class="assignment-card">
|
||||||
|
<div class="top-buttons">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
variant="text"
|
||||||
|
class="back-btn"
|
||||||
|
to="/user/assignment"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-arrow-left</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-chip
|
||||||
|
class="ma-2 top-right-btn"
|
||||||
|
label
|
||||||
|
color="success"
|
||||||
|
>
|
||||||
|
{{ t("submitted") }}
|
||||||
|
</v-chip>
|
||||||
|
</div>
|
||||||
|
<v-card-title class="text-h4">{{ assignment.title }}</v-card-title>
|
||||||
|
<v-card-subtitle class="subtitle-section">
|
||||||
|
<v-btn
|
||||||
|
:to="`/learningPath/${language}/${assignment.learningPath}`"
|
||||||
|
variant="tonal"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{{ t("learning-path") }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-subtitle>
|
||||||
|
|
||||||
|
<v-card-text class="description">
|
||||||
|
{{ assignment.description }}
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-text class="group-section">
|
||||||
|
<h3>{{ t("group") }}</h3>
|
||||||
|
|
||||||
|
<!-- Student view
|
||||||
|
<div v-if="!isTeacher">
|
||||||
|
<div v-if="myGroup">
|
||||||
|
<ul>
|
||||||
|
<li v-for="student in myGroup.members" :key="student.username">
|
||||||
|
{{ student.firstName + ' ' + student.lastName}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>-->
|
||||||
|
|
||||||
|
</v-card-text >
|
||||||
|
<v-card-actions class="justify-end">
|
||||||
|
<v-btn
|
||||||
|
size="large"
|
||||||
|
color="success"
|
||||||
|
variant="flat"
|
||||||
|
@click="submitAssignment"
|
||||||
|
>
|
||||||
|
{{ t("submit") }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@import "@/assets/assignment.css";
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
118
frontend/src/views/assignments/TeacherAssignment.vue
Normal file
118
frontend/src/views/assignments/TeacherAssignment.vue
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
import {ref, computed} from "vue";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
import {AssignmentController, type AssignmentResponse} from "@/controllers/assignments.ts";
|
||||||
|
import {useAssignmentQuery} from "@/queries/assignments.ts";
|
||||||
|
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
||||||
|
|
||||||
|
|
||||||
|
const {t, locale} = useI18n();
|
||||||
|
const language = computed(() => locale.value);
|
||||||
|
const route = useRoute();
|
||||||
|
const assignmentId = ref(Number(route.params.id));
|
||||||
|
const classId = window.history.state?.class_id;
|
||||||
|
const controller = new AssignmentController(classId);
|
||||||
|
|
||||||
|
const assignmentQueryResult = useAssignmentQuery(() => classId, assignmentId);
|
||||||
|
|
||||||
|
/***
|
||||||
|
// Display group members
|
||||||
|
const myGroup = computed(() => {
|
||||||
|
if (!assignment.value || !assignment.value.groups) return null;
|
||||||
|
console.log(assignment.value.groups)
|
||||||
|
return assignment.value.groups.find(group =>
|
||||||
|
group.members.some(m => m.username === myUsername)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
const deleteAssignment = async () => {
|
||||||
|
await controller.deleteAssignment(assignmentId.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<using-query-result
|
||||||
|
:query-result="assignmentQueryResult"
|
||||||
|
v-slot="{ data }: {data: AssignmentResponse}"
|
||||||
|
>
|
||||||
|
<v-card v-if="data" class="assignment-card">
|
||||||
|
<div class="top-buttons">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
variant="text"
|
||||||
|
class="back-btn"
|
||||||
|
to="/user/assignment"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-arrow-left</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
variant="text"
|
||||||
|
class="top-right-btn"
|
||||||
|
@click="deleteAssignment"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<v-card-title class="text-h4">{{ data.title }}</v-card-title>
|
||||||
|
<v-card-subtitle class="subtitle-section">
|
||||||
|
<v-btn
|
||||||
|
:to="`/learningPath/${language}/${data.learningPath}`"
|
||||||
|
variant="tonal"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{{ t("learning-path") }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-subtitle>
|
||||||
|
|
||||||
|
<v-card-text class="description">
|
||||||
|
{{ data.description }}
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-text class="group-section">
|
||||||
|
<h3>{{ t("group") }}</h3>
|
||||||
|
|
||||||
|
<!-- Teacher view
|
||||||
|
<div v-if="isTeacher">
|
||||||
|
<v-expansion-panels>
|
||||||
|
<v-expansion-panel
|
||||||
|
v-for="(group, index) in assignment.groups"
|
||||||
|
:key="group.id"
|
||||||
|
>
|
||||||
|
<v-expansion-panel-title>
|
||||||
|
{{ t("group") }} {{ index + 1 }}
|
||||||
|
</v-expansion-panel-title>
|
||||||
|
<v-expansion-panel-text>
|
||||||
|
<ul>
|
||||||
|
<li v-for="student in group.members" :key="student.username">
|
||||||
|
{{ student.firstName + ' ' + student.lastName }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</v-expansion-panel-text>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
|
</div>-->
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions class="justify-end">
|
||||||
|
<v-btn
|
||||||
|
size="large"
|
||||||
|
color="success"
|
||||||
|
variant="text"
|
||||||
|
>
|
||||||
|
{{ t("view-submissions") }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</using-query-result>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@import "@/assets/assignment.css";
|
||||||
|
</style>
|
||||||
|
|
|
@ -27,14 +27,15 @@ if (isTeacher.value) {
|
||||||
classesQueryResults = useStudentClassesQuery(username, true);
|
classesQueryResults = useStudentClassesQuery(username, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: replace with query from classes
|
||||||
const classController = new ClassController();
|
const classController = new ClassController();
|
||||||
|
|
||||||
|
|
||||||
const assignments = asyncComputed(async () => {
|
const assignments = asyncComputed(async () => {
|
||||||
const classes = classesQueryResults?.data?.value?.classes;
|
const classes = classesQueryResults?.data?.value?.classes;
|
||||||
if (!classes) return [];
|
if (!classes) return [];
|
||||||
const result = await Promise.all(
|
const result = await Promise.all(
|
||||||
(classes as ClassDTO[]).map(async (cls) => {
|
(classes as ClassDTO[]).map(async (cls) => {
|
||||||
|
//TODO: replace by class queries
|
||||||
const {assignments} = await classController.getAssignments(cls.id);
|
const {assignments} = await classController.getAssignments(cls.id);
|
||||||
return assignments.map(a => ({
|
return assignments.map(a => ({
|
||||||
id: a.id,
|
id: a.id,
|
||||||
|
@ -51,8 +52,6 @@ const assignments = asyncComputed(async () => {
|
||||||
return result.flat();
|
return result.flat();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
console.log(assignments);
|
|
||||||
|
|
||||||
|
|
||||||
const goToCreateAssignment = async () => {
|
const goToCreateAssignment = async () => {
|
||||||
await router.push('/assignment/create');
|
await router.push('/assignment/create');
|
||||||
|
@ -67,6 +66,7 @@ const goToAssignmentDetails = async (id: number, class_id: string) => {
|
||||||
|
|
||||||
|
|
||||||
const goToDeleteAssignment = async (id: number, class_id: string) => {
|
const goToDeleteAssignment = async (id: number, class_id: string) => {
|
||||||
|
//TODO: replace with query
|
||||||
const controller = new AssignmentController(class_id);
|
const controller = new AssignmentController(class_id);
|
||||||
await controller.deleteAssignment(id);
|
await controller.deleteAssignment(id);
|
||||||
};
|
};
|
||||||
|
@ -98,32 +98,30 @@ onMounted(async () => {
|
||||||
cols="12"
|
cols="12"
|
||||||
>
|
>
|
||||||
<v-card class="assignment-card">
|
<v-card class="assignment-card">
|
||||||
<v-card-text class="card-content">
|
<div class="top-content">
|
||||||
<div class="top-content">
|
<div class="assignment-title">{{ assignment.title }}</div>
|
||||||
<div class="assignment-title">{{ assignment.title }}</div>
|
<div class="assignment-class">
|
||||||
<div class="assignment-class">
|
{{ t('class') }}:
|
||||||
{{ t('class') }}:
|
<span class="class-name">
|
||||||
<span class="class-name">
|
|
||||||
{{ assignment.class.displayName }}
|
{{ assignment.class.displayName }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
|
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
<v-btn color="primary"
|
<v-btn color="primary"
|
||||||
size="small"
|
variant="text"
|
||||||
@click="goToAssignmentDetails(assignment.id, assignment.class.id)">
|
@click="goToAssignmentDetails(assignment.id, assignment.class.id)">
|
||||||
{{ t('view-assignment') }}
|
{{ t('view-assignment') }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn v-if="isTeacher" color="red"
|
<v-btn v-if="isTeacher" color="red"
|
||||||
size="small"
|
variant="text"
|
||||||
@click="goToDeleteAssignment(assignment.id, assignment.class.id)">
|
@click="goToDeleteAssignment(assignment.id, assignment.class.id)">
|
||||||
{{ t('delete') }}
|
{{ t('delete') }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue