feat: edit assignments

This commit is contained in:
laurejablonski 2025-05-04 10:25:02 +02:00
parent 6c28c0fc3d
commit 20173169b7
2 changed files with 331 additions and 170 deletions

View file

@ -18,12 +18,22 @@
font-size: 1.1rem;
}
.top-right-btn {
position: absolute;
right: 2%;
color: red;
.top-buttons-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
position: relative;
}
.right-buttons {
display: flex;
gap: 0.5rem;
align-items: center;
color: #0e6942
}
.group-section {
margin-top: 2rem;
}

View file

@ -4,12 +4,12 @@
import { useAssignmentQuery, useDeleteAssignmentMutation } from "@/queries/assignments.ts";
import UsingQueryResult from "@/components/UsingQueryResult.vue";
import { useGroupsQuery } from "@/queries/groups.ts";
import { useGetLearningPathQuery } from "@/queries/learning-paths.ts";
import { useGetAllLearningPaths, useGetLearningPathQuery } from "@/queries/learning-paths.ts";
import type { Language } from "@/data-objects/language.ts";
import type { AssignmentResponse } from "@/controllers/assignments.ts";
import type { GroupDTO } from "@dwengo-1/common/interfaces/group";
import type { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment";
import type { StudentDTO } from "@dwengo-1/common/interfaces/student";
import type { StudentDTO } from "@dwengo-1/common/interfaces/student";
import type { LearningPath } from "@/data-objects/learning-paths/learning-path";
const props = defineProps<{
classId: string;
@ -21,19 +21,43 @@ import type { StudentDTO } from "@dwengo-1/common/interfaces/student";
) => { groupProgressMap: Map<number, number> };
}>();
const isEditing = ref(false);
const { t, locale } = useI18n();
const language = computed(() => locale.value);
const groups = ref();
const learningPath = ref();
const editingLearningPath = ref(learningPath);
const description = ref("");
const assignmentQueryResult = useAssignmentQuery(props.classId, props.assignmentId);
learningPath.value = assignmentQueryResult.data.value?.assignment?.learningPath;
const learningPathsQueryResults = useGetAllLearningPaths(language);
// Get learning path object
const lpQueryResult = useGetLearningPathQuery(
computed(() => assignmentQueryResult.data.value?.assignment?.learningPath ?? ""),
computed(() => language.value as Language),
);
const learningPathRules = [
(value: { hruid: string; title: string }): string | boolean => {
if (value && value.hruid) {
return true; // Valid if hruid is present
}
return "You must select a learning path.";
},
];
const descriptionRules = [
(value: string): string | boolean => {
if (!value || value.trim() === "") {
return "Description cannot be empty.";
}
return true;
},
];
// Get all the groups withing the assignment
const groupsQueryResult = useGroupsQuery(props.classId, props.assignmentId, true);
groups.value = groupsQueryResult.data.value?.groups;
@ -84,6 +108,13 @@ Const {groupProgressMap} = props.useGroupsWithProgress(
},
);
}
function saveChanges() {
//TODO
const new_learningpath = editingLearningPath.value;
const new_description = description.value;
isEditing.value = false;
}
</script>
<template>
@ -92,128 +123,225 @@ Const {groupProgressMap} = props.useGroupsWithProgress(
:query-result="assignmentQueryResult"
v-slot="assignmentResponse: { data: AssignmentResponse }"
>
<v-container fluid class="ma-4">
<v-row no-gutters class="custom-breakpoint">
<v-col cols="12" sm="6" md="6" class="responsive-col">
<v-card
v-if="assignmentResponse"
class="assignment-card"
<v-container
fluid
class="ma-4"
>
<v-row
no-gutters
class="custom-breakpoint"
>
<v-col
cols="12"
sm="6"
md="6"
class="responsive-col"
>
<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(assignmentResponse.data.assignment.id, assignmentResponse.data.assignment.within)"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
</div>
<v-card-title class="text-h4 assignmentTopTitle">{{ assignmentResponse.data.assignment.title }}</v-card-title>
<v-card-subtitle class="subtitle-section">
<using-query-result
:query-result="lpQueryResult"
v-slot="{ data: lpData }"
>
<v-btn
v-if="lpData"
:to="`/learningPath/${lpData.hruid}/${language}/${lpData.startNode.learningobjectHruid}?assignmentNo=${assignmentId}&classId=${classId}`"
variant="tonal"
color="primary"
>
{{ t("learning-path") }}
</v-btn>
</using-query-result>
</v-card-subtitle>
<v-card-text class="description">
{{ assignmentResponse.data.assignment.description }}
</v-card-text>
<v-card-text class="group-section">
<h3>{{ t("groups") }}</h3>
<div class="table-scroll">
<v-data-table
:headers="headers"
:items="allGroups"
item-key="id"
class="elevation-1"
>
<template #[`item.name`]="{ item }">
<v-btn
@click="openGroupDetails(item)"
variant="text"
color="primary"
>
{{ item.name }}
</v-btn>
</template>
<template #[`item.progress`]="{ item }">
<v-progress-linear
:model-value="item.progress"
color="blue-grey"
height="25"
>
<template v-slot:default="{ value }">
<strong>{{ Math.ceil(value) }}%</strong>
</template>
</v-progress-linear>
</template>
<template #[`item.submission`]="{ item }">
<v-btn
:to="item.submitted ? `${props.assignmentId}/submissions/` : undefined"
:color="item.submitted ? 'green' : 'red'"
variant="text"
class="text-capitalize"
>
{{ item.submitted ? t("see-submission") : t("no-submission") }}
</v-btn>
</template>
</v-data-table>
</div>
</v-card-text>
<v-dialog
v-model="dialog"
max-width="50%"
<v-card
v-if="assignmentResponse"
class="assignment-card"
>
<v-card>
<v-card-title class="headline">{{ t("members") }}</v-card-title>
<v-card-text>
<v-list>
<v-list-item
v-for="(member, index) in selectedGroup.members"
:key="index"
>
<v-list-item-content>
<v-list-item-title
>{{ member.firstName + " " + member.lastName }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card-text>
<v-card-actions>
<div class="top-buttons">
<div class="top-buttons-wrapper">
<v-btn
color="primary"
@click="dialog = false"
>Close</v-btn
icon
variant="text"
class="back-btn"
to="/user/assignment"
>
</v-card-actions>
</v-card>
</v-dialog>
<!--
<v-icon>mdi-arrow-left</v-icon>
</v-btn>
<div class="right-buttons">
<v-btn
v-if="!isEditing"
icon
variant="text"
class="top_next_to_right_button"
@click="
() => {
isEditing = true;
description = assignmentResponse.data.assignment.description;
}
"
>
<v-icon>mdi-pencil</v-icon>
</v-btn>
<v-btn
v-else
variant="text"
class="top-right-btn"
@click="() => {isEditing = false; editingLearningPath=learningPath}"
>{{ t("cancel") }}</v-btn
>
<v-btn
v-if="!isEditing"
icon
variant="text"
class="top-right-btn"
@click="
deleteAssignment(
assignmentResponse.data.assignment.id,
assignmentResponse.data.assignment.within,
)
"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-btn
v-else
icon
variant="text"
class="top_next_to_right_button"
@click="saveChanges"
>
<v-icon>mdi-content-save-edit-outline</v-icon>
</v-btn>
</div>
</div>
</div>
<v-card-title class="text-h4 assignmentTopTitle">{{
assignmentResponse.data.assignment.title
}}</v-card-title>
<v-card-subtitle
v-if="!isEditing"
class="subtitle-section"
>
<using-query-result
:query-result="lpQueryResult"
v-slot="{ data: lpData }"
>
<v-btn
v-if="lpData"
:to="`/learningPath/${lpData.hruid}/${language}/${lpData.startNode.learningobjectHruid}?assignmentNo=${assignmentId}&classId=${classId}`"
variant="tonal"
color="primary"
>
{{ t("learning-path") }}
</v-btn>
</using-query-result>
</v-card-subtitle>
<using-query-result
v-else
:query-result="learningPathsQueryResults"
v-slot="{ data }: { data: LearningPath[] }"
>
<v-card-text>
<v-combobox
v-model="editingLearningPath"
:items="data"
:label="t('choose-lp')"
:rules="learningPathRules"
variant="outlined"
clearable
hide-details
density="compact"
append-inner-icon="mdi-magnify"
item-title="title"
item-value="hruid"
required
:filter="
(item, query: string) =>
item.title.toLowerCase().includes(query.toLowerCase())
"
></v-combobox>
</v-card-text>
</using-query-result>
<v-card-text
v-if="!isEditing"
class="description"
>
{{ assignmentResponse.data.assignment.description }}
</v-card-text>
<v-card-text v-else>
<v-textarea
v-model="description"
:label="t('description')"
variant="outlined"
density="compact"
auto-grow
rows="3"
:rules="descriptionRules"
></v-textarea>
</v-card-text>
<v-card-text class="group-section">
<h3>{{ t("groups") }}</h3>
<div class="table-scroll">
<v-data-table
:headers="headers"
:items="allGroups"
item-key="id"
class="elevation-1"
>
<template #[`item.name`]="{ item }">
<v-btn
@click="openGroupDetails(item)"
variant="text"
color="primary"
>
{{ item.name }}
</v-btn>
</template>
<template #[`item.progress`]="{ item }">
<v-progress-linear
:model-value="item.progress"
color="blue-grey"
height="25"
>
<template v-slot:default="{ value }">
<strong>{{ Math.ceil(value) }}%</strong>
</template>
</v-progress-linear>
</template>
<template #[`item.submission`]="{ item }">
<v-btn
:to="item.submitted ? `${props.assignmentId}/submissions/` : undefined"
:color="item.submitted ? 'green' : 'red'"
variant="text"
class="text-capitalize"
>
{{ item.submitted ? t("see-submission") : t("no-submission") }}
</v-btn>
</template>
</v-data-table>
</div>
</v-card-text>
<v-dialog
v-model="dialog"
max-width="50%"
>
<v-card>
<v-card-title class="headline">{{ t("members") }}</v-card-title>
<v-card-text>
<v-list>
<v-list-item
v-for="(member, index) in selectedGroup.members"
:key="index"
>
<v-list-item-content>
<v-list-item-title
>{{ member.firstName + " " + member.lastName }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card-text>
<v-card-actions>
<v-btn
color="primary"
@click="dialog = false"
>Close</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
<!--
<v-card-actions class="justify-end">
<v-btn
size="large"
@ -224,51 +352,74 @@ Const {groupProgressMap} = props.useGroupsWithProgress(
</v-btn>
</v-card-actions>
-->
</v-card>
</v-col>
<v-col cols="12" sm="6" md="6" class="responsive-col">
<v-table class="table">
<thead>
<tr>
<th class="header">{{ t("group") }}</th>
<th class="header">
{{ t("progress") }}
</th>
<th class="header">{{ t("Members") }}</th>
<th class="header">
<v-btn :to="`/assignment/${assignmentResponse.data.assignment.within}/${assignmentResponse.data.assignment.id}`" variant="text">
<v-icon> mdi-pencil </v-icon>
</v-btn>
</th>
</tr>
</thead>
<tbody>
<tr v-for="g in assignmentResponse.data.assignment.groups as GroupDTO[]" :key="g.groupNumber">
<td>
<v-btn :to="`/assignment/${assignmentResponse.data.assignment.within}/${assignmentResponse.data.assignment.id}`" variant="text">
{{ g.groupNumber }}
<v-icon end> mdi-menu-right </v-icon>
</v-btn>
</td>
<td>
<v-progress-linear :model-value="0" color="blue-grey" height="25">
<template v-slot:default="{ value }">
<strong>{{ Math.ceil(value) }}%</strong>
</template>
</v-progress-linear>
</td>
<td>{{ (g.members! as StudentDTO[]).map(member => member.username).join(', ') }}</td>
<td>
<v-btn :to="`/assignment/${assignmentResponse.data.assignment.within}/${assignmentResponse.data.assignment.id}`" variant="text">
<v-icon color="red"> mdi-delete </v-icon>
</v-btn>
</td>
</tr>
</tbody>
</v-table>
</v-col>
</v-row>
</v-container>
</v-card>
</v-col>
<v-col
cols="12"
sm="6"
md="6"
class="responsive-col"
>
<v-table class="table">
<thead>
<tr>
<th class="header">{{ t("group") }}</th>
<th class="header">
{{ t("progress") }}
</th>
<th class="header">{{ t("Members") }}</th>
<th class="header">
<v-btn
:to="`/assignment/${assignmentResponse.data.assignment.within}/${assignmentResponse.data.assignment.id}`"
variant="text"
>
<v-icon> mdi-pencil </v-icon>
</v-btn>
</th>
</tr>
</thead>
<tbody>
<tr
v-for="g in assignmentResponse.data.assignment.groups as GroupDTO[]"
:key="g.groupNumber"
>
<td>
<v-btn
:to="`/assignment/${assignmentResponse.data.assignment.within}/${assignmentResponse.data.assignment.id}`"
variant="text"
>
{{ g.groupNumber }}
<v-icon end> mdi-menu-right </v-icon>
</v-btn>
</td>
<td>
<v-progress-linear
:model-value="0"
color="blue-grey"
height="25"
>
<template v-slot:default="{ value }">
<strong>{{ Math.ceil(value) }}%</strong>
</template>
</v-progress-linear>
</td>
<td>
{{ (g.members! as StudentDTO[]).map((member) => member.username).join(", ") }}
</td>
<td>
<v-btn
:to="`/assignment/${assignmentResponse.data.assignment.within}/${assignmentResponse.data.assignment.id}`"
variant="text"
>
<v-icon color="red"> mdi-delete </v-icon>
</v-btn>
</td>
</tr>
</tbody>
</v-table>
</v-col>
</v-row>
</v-container>
</using-query-result>
</div>
</template>