Skip to content

Assigment: Fix corrections, rating, comment replies and submission deadline check - refs BT#22568 #6277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 14 additions & 27 deletions assets/vue/components/assignments/AssignmentsForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -261,18 +261,11 @@ const v$ = useVuelidate(rules, assignment)

const onSubmit = async () => {
const result = await v$.value.$validate()

if (!result) {
return
}
if (!result) return

const publicationStudent = {
title: assignment.title,
description: assignment.description,
assignment: {
expiresOn: null,
endsOn: null,
},
parentResourceNode: route.params.node * 1,
resourceLinkList: [
{
Expand All @@ -282,32 +275,26 @@ const onSubmit = async () => {
visibility: RESOURCE_LINK_PUBLISHED,
},
],
qualification: assignment.qualification,
addToCalendar: assignment.addToCalendar,
allowTextAssignment: assignment.allowTextAssignment.value,
}

if (showAdvancedSettings.value) {
publicationStudent.qualification = assignment.qualification

publicationStudent.addToCalendar = assignment.addToCalendar

if (chkAddToGradebook.value) {
publicationStudent.gradebookCategoryId = assignment.gradebookId.id
publicationStudent.weight = assignment.weight
}

if (chkExpiresOn.value) {
publicationStudent.assignment.expiresOn = assignment.expiresOn
}
if (chkAddToGradebook.value) {
publicationStudent.gradebookCategoryId = assignment.gradebookId.id
publicationStudent.weight = assignment.weight
}

if (chkEndsOn.value) {
publicationStudent.assignment.endsOn = assignment.endsOn
}
if (chkExpiresOn.value) {
publicationStudent.expiresOn = assignment.expiresOn.toISOString()
}

publicationStudent.allowTextAssignment = assignment.allowTextAssignment.value
if (chkEndsOn.value) {
publicationStudent.endsOn = assignment.endsOn.toISOString()
}

if (props.defaultAssignment) {
if (props.defaultAssignment?.["@id"]) {
publicationStudent["@id"] = props.defaultAssignment["@id"]
publicationStudent.assignment["@id"] = props.defaultAssignment.assignment["@id"]
}

emit("submit", publicationStudent)
Expand Down
52 changes: 32 additions & 20 deletions assets/vue/components/assignments/CorrectAndRateModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@
rows="5"
/>

<div class="flex flex-col gap-2">
<label>{{ t("Score") }}</label>

<template v-if="!forceStudentView">
<input
type="number"
v-model.number="qualification"
class="input border p-2 rounded"
min="0"
step="0.1"
/>
</template>

<template v-else>
<span class="border p-2 rounded bg-gray-100 text-sm">
{{ qualification ?? t("Not graded yet") }}
</span>
</template>
</div>

<div class="flex flex-col gap-2">
<label>{{ t("Attach file (optional)") }}</label>
<input
Expand Down Expand Up @@ -98,6 +118,8 @@ import Button from "primevue/button"
import Dialog from "primevue/dialog"
import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"
import cStudentPublicationService from "../../services/cstudentpublication"
import { useRoute } from "vue-router"
import { useSecurityStore } from "../../store/securityStore"

const props = defineProps({
modelValue: Boolean,
Expand All @@ -112,6 +134,13 @@ const sendMail = ref(false)
const selectedFile = ref(null)
const { t } = useI18n()
const notification = useNotification()
const qualification = ref(null)
const route = useRoute()
const parentResourceNodeId = parseInt(route.params.node)
const securityStore = useSecurityStore()
const isEditor = securityStore.isCourseAdmin || securityStore.isTeacher
const isStudentView = route.query.isStudentView === "true"
const forceStudentView = !isEditor || isStudentView

watch(
() => props.modelValue,
Expand All @@ -121,6 +150,7 @@ watch(
comment.value = ""
sendMail.value = false
selectedFile.value = null
qualification.value = props.item.qualification ?? null
comments.value = await cStudentPublicationService.loadComments(props.item.iid)
}
},
Expand Down Expand Up @@ -163,13 +193,9 @@ async function submit() {
formData.append("uploadFile", selectedFile.value)
}
formData.append("comment", comment.value)
formData.append("qualification", qualification.value ?? "")

await cStudentPublicationService.uploadComment(
props.item.iid,
getResourceNodeId(props.item.resourceNode),
formData,
sendMail.value,
)
await cStudentPublicationService.uploadComment(props.item.iid, parentResourceNodeId, formData, sendMail.value)

notification.showSuccessNotification(t("Comment added successfully"))

Expand All @@ -180,18 +206,4 @@ async function submit() {
notification.showErrorNotification(error)
}
}

function getResourceNodeId(resourceNode) {
if (!resourceNode) return 0

if (typeof resourceNode === "object" && "id" in resourceNode) {
return parseInt(resourceNode.id, 10)
}

const idString = typeof resourceNode === "string" ? resourceNode : resourceNode["@id"]
if (!idString || typeof idString !== "string") return 0

const match = idString.match(/\/(\d+)$/)
return match ? parseInt(match[1], 10) : 0
}
</script>
20 changes: 12 additions & 8 deletions assets/vue/services/cstudentpublication.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ async function findStudentAssignments() {
return response.json()
}

async function getAssignmentMetadata(assignmentId) {
const { sid, cid, gid } = useCidReq()
const params = new URLSearchParams({ cid, ...(sid && { sid }), ...(gid && { gid }) }).toString()
async function getAssignmentMetadata(assignmentId, cid, sid = 0, gid = 0) {
const params = new URLSearchParams({
cid,
...(sid && { sid }),
...(gid && { gid }),
}).toString()

const response = await axios.get(`${ENTRYPOINT}c_student_publications/${assignmentId}?${params}`)
return response.data
}
Expand Down Expand Up @@ -106,15 +110,15 @@ async function sendEmailToUnsubmitted(assignmentId, queryParams = {}) {
return response.data
}

async function deleteAllCorrections(assignmentId) {
const { cid, sid } = useCidReq()
async function deleteAllCorrections(assignmentId, cid, sid = 0) {
const params = { cid, ...(sid && { sid }) }

await axios.delete(`/assignments/${assignmentId}/corrections/delete`, {
params: { cid, ...(sid && { sid }) },
params,
})
}

async function exportAssignmentPdf(assignmentId) {
const { cid, sid, gid } = useCidReq()
async function exportAssignmentPdf(assignmentId, cid, sid = 0, gid = 0) {
const params = { cid, ...(sid && { sid }), ...(gid && { gid }) }

const response = await axios.get(`/assignments/${assignmentId}/export/pdf`, {
Expand Down
32 changes: 25 additions & 7 deletions assets/vue/views/assignments/AssignmentDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<template v-if="forceStudentView">
<div class="ml-auto">
<BaseButton
v-if="!isAfterDeadline"
icon="upload"
:label="t('Upload my assignment')"
type="primary"
Expand Down Expand Up @@ -72,6 +73,13 @@
</template>
</div>

<div
v-if="forceStudentView && isAfterDeadline"
class="text-red-600 border border-red-300 p-4 rounded bg-red-50"
>
{{ t("You can no longer submit. The deadline has passed.") }}
</div>

<h2 class="text-2xl font-bold">{{ assignment?.title }}</h2>

<div class="bg-gray-10 border border-gray-25 rounded-lg shadow-sm">
Expand Down Expand Up @@ -115,7 +123,7 @@
</template>

<script setup>
import { onMounted, ref } from "vue"
import { computed, onMounted, ref } from "vue"
import { useRoute, useRouter } from "vue-router"
import { useI18n } from "vue-i18n"
import { useCidReq } from "../../composables/cidReq"
Expand Down Expand Up @@ -144,6 +152,16 @@ const addedDocuments = ref([])
const notification = useNotification()
const submissionListKey = ref(0)

const isAfterDeadline = computed(() => {
if (!assignment.value?.assignment?.endsOn) {
return false
}

const now = new Date()
const deadline = new Date(assignment.value.assignment.endsOn)
return now > deadline
})

async function loadAddedDocuments() {
try {
const response = await axios.get(`${ENTRYPOINT}c_student_publication_rel_documents`, {
Expand All @@ -157,7 +175,7 @@ async function loadAddedDocuments() {

onMounted(async () => {
try {
const response = await cStudentPublicationService.getAssignmentMetadata(assignmentId)
const response = await cStudentPublicationService.getAssignmentMetadata(assignmentId, cid, sid, gid)
assignment.value = response
await loadAddedDocuments()
} catch (error) {
Expand Down Expand Up @@ -194,7 +212,7 @@ function editAssignment() {

async function exportPdf() {
try {
const data = await cStudentPublicationService.exportAssignmentPdf(assignmentId)
const data = await cStudentPublicationService.exportAssignmentPdf(assignmentId, cid, sid, gid)
const url = window.URL.createObjectURL(new Blob([data], { type: "application/pdf" }))
const link = document.createElement("a")
link.href = url
Expand All @@ -220,15 +238,15 @@ function addDocument() {
router.push({
name: "AssignmentAddDocument",
params: { id: assignmentId },
query: route.query,
query: { cid, sid, gid },
})
}

function addUsers() {
router.push({
name: "AssignmentAddUser",
params: { id: assignmentId },
query: route.query,
query: { cid, sid, gid },
})
}

Expand Down Expand Up @@ -276,10 +294,10 @@ async function deleteAllCorrections() {
if (!confirm(t("Are you sure you want to delete all corrections?"))) return

try {
await cStudentPublicationService.deleteAllCorrections(assignmentId)
await cStudentPublicationService.deleteAllCorrections(assignmentId, cid, sid)
notification.showSuccessNotification(t("All corrections deleted"))

assignment.value = await cStudentPublicationService.getAssignmentMetadata(assignmentId)
assignment.value = await cStudentPublicationService.getAssignmentMetadata(assignmentId, cid, sid, gid)

submissionListKey.value++
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion assets/vue/views/assignments/AssignmentMissing.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ onMounted(async () => {
loading.value = true
try {
users.value = await cstudentpublication.getUnsubmittedUsers(props.id)
assignment.value = await cstudentpublication.getAssignmentMetadata(props.id)
assignment.value = await cstudentpublication.getAssignmentMetadata(props.id, cid, sid, gid)
} catch (error) {
console.error("Error loading data", error)
} finally {
Expand Down
44 changes: 41 additions & 3 deletions assets/vue/views/assignments/AssignmentSubmit.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
<template>
<div class="p-4">
<h2 class="text-xl font-bold mb-4">{{ t("Upload your assignment") }}</h2>
<div>
<div class="flex items-center justify-between">
<BaseIcon
icon="back"
size="big"
@click="goBack"
:title="t('Back')"
/>
</div>
<hr />
<h1 class="text-2xl font-bold">{{ t("Upload your assignment") }} - {{ publicationTitle }}</h1>
<Dashboard
:uppy="uppy"
:props="{ width: '100%', height: 400 }"
Expand All @@ -9,7 +18,7 @@
</template>

<script setup>
import { ref } from "vue"
import { onMounted, ref } from "vue"
import { useRoute, useRouter } from "vue-router"
import { useCidReq } from "../../composables/cidReq"
import { useI18n } from "vue-i18n"
Expand All @@ -20,16 +29,37 @@ import "@uppy/dashboard/dist/style.css"
import { Dashboard } from "@uppy/vue"
import Uppy from "@uppy/core"
import XHRUpload from "@uppy/xhr-upload"
import BaseIcon from "../../components/basecomponents/BaseIcon.vue"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"

const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const { cid, sid, gid } = useCidReq()
const { showSuccessNotification, showErrorNotification } = useNotification()
const parentResourceNodeId = Number(route.params.node)
const publicationId = parseInt(route.params.id)
const publicationTitle = ref("")

const queryParams = new URLSearchParams({ cid, ...(sid && { sid }), ...(gid && { gid }) }).toString()

onMounted(() => {
loadPublicationMetadata()
})

async function loadPublicationMetadata() {
try {
const response = await axios.get(`${ENTRYPOINT}c_student_publications/${publicationId}`, {
params: { cid, ...(sid && { sid }), ...(gid && { gid }) },
})
const data = response.data
publicationTitle.value = data.title
} catch (e) {
console.error("Error loading publication metadata", e)
}
}

const uppy = ref(
new Uppy({
restrictions: {
Expand Down Expand Up @@ -66,4 +96,12 @@ uppy.value.on("file-added", async (file) => {
showErrorNotification(error)
}
})

function goBack() {
router.push({
name: "AssignmentDetail",
params: { id: publicationId },
query: route.query,
})
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ public function __invoke(
$commentEntity->setFile($filename);
}

$qualification = (float) $request->get('qualification', null);

if (null !== $qualification) {
$submission->setQualification($qualification);
$submission->setQualificatorId($user->getId());
$submission->setDateOfQualification(new \DateTime());
}

$em->persist($commentEntity);
$em->flush();

Expand Down
Loading
Loading