Skip to content

Commit 81aaab4

Browse files
Merge pull request #6291 from christianbeeznest/fide-22568-3
Assigment: Fix score input, max grade validation, feedback display, and PDF export in assignment corrections - refs BT#22568
2 parents 3fd8617 + 7ddf23a commit 81aaab4

File tree

8 files changed

+162
-30
lines changed

8 files changed

+162
-30
lines changed

assets/vue/components/assignments/CorrectAndRateModal.vue

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@
3131
v-model.number="qualification"
3232
class="input border p-2 rounded"
3333
min="0"
34+
:max="maxQualification"
3435
step="0.1"
3536
/>
37+
<template v-if="maxQualification">
38+
<span class="text-xs text-gray-600"> {{ t("Max grade") }}: {{ maxQualification }} </span>
39+
</template>
3640
</template>
3741

3842
<template v-else>
@@ -67,6 +71,7 @@
6771
/>
6872
<Button
6973
:label="t('Send')"
74+
:disabled="submitting"
7075
@click="submit"
7176
/>
7277
</div>
@@ -110,7 +115,7 @@
110115
</template>
111116

112117
<script setup>
113-
import { ref, watch } from "vue"
118+
import { computed, ref, watch } from "vue"
114119
import { useNotification } from "../../composables/notification"
115120
import { useI18n } from "vue-i18n"
116121
import Textarea from "primevue/textarea"
@@ -141,6 +146,10 @@ const securityStore = useSecurityStore()
141146
const isEditor = securityStore.isCourseAdmin || securityStore.isTeacher
142147
const isStudentView = route.query.isStudentView === "true"
143148
const forceStudentView = !isEditor || isStudentView
149+
const maxQualification = computed(() =>
150+
props.item?.publicationParent?.qualification ?? null
151+
)
152+
const submitting = ref(false)
144153
145154
watch(
146155
() => props.modelValue,
@@ -182,28 +191,59 @@ function handleFileUpload(event) {
182191
}
183192
184193
async function submit() {
185-
if (!comment.value.trim()) {
186-
notification.showErrorNotification(t("Comment is required"))
194+
if (submitting.value) return
195+
submitting.value = true
196+
197+
const trimmedComment = comment.value.trim()
198+
199+
const hasComment = trimmedComment.length > 0
200+
const hasFile = !!selectedFile.value
201+
const hasQualificationChange = qualification.value !== props.item.qualification
202+
203+
if (!hasComment && !hasFile && !hasQualificationChange) {
204+
notification.showErrorNotification(t("Please add a comment, a grade or a file"))
205+
submitting.value = false
206+
return
207+
}
208+
209+
if (!hasComment && !hasFile && hasQualificationChange) {
210+
try {
211+
await cStudentPublicationService.updateScore(props.item.iid, qualification.value)
212+
notification.showSuccessNotification(t("Score updated successfully"))
213+
emit("commentSent")
214+
close()
215+
} catch (error) {
216+
notification.showErrorNotification(error)
217+
} finally {
218+
submitting.value = false
219+
}
187220
return
188221
}
189222
190223
try {
191224
const formData = new FormData()
225+
formData.append("submissionId", props.item.iid)
226+
formData.append("qualification", qualification.value ?? "")
227+
192228
if (selectedFile.value) {
193229
formData.append("uploadFile", selectedFile.value)
194230
}
195-
formData.append("comment", comment.value)
196-
formData.append("qualification", qualification.value ?? "")
231+
232+
if (hasComment) {
233+
formData.append("comment", trimmedComment)
234+
}
197235
198236
await cStudentPublicationService.uploadComment(props.item.iid, parentResourceNodeId, formData, sendMail.value)
199237
200238
notification.showSuccessNotification(t("Comment added successfully"))
201-
202239
comments.value = await cStudentPublicationService.loadComments(props.item.iid)
203240
comment.value = ""
204241
selectedFile.value = null
242+
emit("commentSent")
205243
} catch (error) {
206244
notification.showErrorNotification(error)
245+
} finally {
246+
submitting.value = false
207247
}
208248
}
209249
</script>

assets/vue/components/assignments/StudentSubmissionList.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,28 @@
6262
</template>
6363
</Column>
6464

65+
<Column :header="t('Score')">
66+
<template #body="{ data }">
67+
<template v-if="data.qualification !== null && data.publicationParent?.qualification">
68+
<span
69+
:class="{
70+
'bg-success/10 text-success font-semibold text-sm px-2 py-1 rounded':
71+
data.qualification > data.publicationParent.qualification / 2,
72+
'bg-danger/10 text-danger font-semibold text-sm px-2 py-1 rounded':
73+
data.qualification <= data.publicationParent.qualification / 2,
74+
}"
75+
>
76+
{{ data.qualification.toFixed(1) }} / {{ data.publicationParent.qualification.toFixed(1) }}
77+
</span>
78+
</template>
79+
<template v-else>
80+
<span class="text-gray-50">
81+
{{ t("Not graded yet") }}
82+
</span>
83+
</template>
84+
</template>
85+
</Column>
86+
6587
<Column
6688
field="sentDate"
6789
:header="t('Date')"

assets/vue/components/assignments/TeacherSubmissionList.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,28 @@
5858
</template>
5959
</Column>
6060

61+
<Column :header="t('Score')">
62+
<template #body="{ data }">
63+
<template v-if="data.qualification !== null && data.publicationParent?.qualification">
64+
<span
65+
:class="{
66+
'bg-success/10 text-success font-semibold text-sm px-2 py-1 rounded':
67+
data.qualification > data.publicationParent.qualification / 2,
68+
'bg-danger/10 text-danger font-semibold text-sm px-2 py-1 rounded':
69+
data.qualification <= data.publicationParent.qualification / 2,
70+
}"
71+
>
72+
{{ data.qualification.toFixed(1) }} / {{ data.publicationParent.qualification.toFixed(1) }}
73+
</span>
74+
</template>
75+
<template v-else>
76+
<span class="text-gray-50">
77+
{{ t("Not graded yet") }}
78+
</span>
79+
</template>
80+
</template>
81+
</Column>
82+
6183
<Column
6284
field="sentDate"
6385
:header="t('Date')"

assets/vue/services/cstudentpublication.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ async function uploadCorrectionsPackage(assignmentId, file) {
148148
return response.data
149149
}
150150

151+
async function updateScore(iid, qualification) {
152+
return axios.put(`${ENTRYPOINT}c_student_publications/${iid}`, {
153+
qualification: qualification,
154+
})
155+
}
156+
151157
export default {
152158
...makeService("c_student_publications"),
153159
findStudentAssignments,
@@ -167,4 +173,5 @@ export default {
167173
exportAssignmentPdf,
168174
downloadAssignments,
169175
uploadCorrectionsPackage,
176+
updateScore,
170177
}

src/CoreBundle/Controller/Api/CreateStudentPublicationCommentAction.php

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,24 @@ public function __invoke(
3636

3737
$commentEntity = new CStudentPublicationComment();
3838

39-
$result = $this->handleCreateCommentRequest(
40-
$commentEntity,
41-
$commentRepo,
42-
$request,
43-
$em,
44-
$fileExistsOption,
45-
$translator
46-
);
39+
$hasFile = $request->files->get('uploadFile');
40+
$hasComment = trim((string) $request->get('comment')) !== '';
41+
42+
if ($hasFile || $hasComment) {
43+
$result = $this->handleCreateCommentRequest(
44+
$commentEntity,
45+
$commentRepo,
46+
$request,
47+
$em,
48+
$fileExistsOption,
49+
$translator
50+
);
51+
52+
$filename = $result['filename'] ?? null;
53+
if (!empty($filename)) {
54+
$commentEntity->setFile($filename);
55+
}
56+
}
4757

4858
$commentText = $request->get('comment');
4959
$submissionId = (int) $request->get('submissionId');
@@ -62,24 +72,29 @@ public function __invoke(
6272
/** @var User $user */
6373
$user = $security->getUser();
6474

65-
$commentEntity->setUser($user);
66-
$commentEntity->setPublication($submission);
67-
$commentEntity->setComment($commentText ?? '');
75+
$qualification = $request->get('qualification', null);
76+
$hasQualification = $qualification !== null;
6877

69-
$filename = $result['filename'] ?? null;
70-
if (!empty($filename)) {
71-
$commentEntity->setFile($filename);
72-
}
78+
if ($hasFile || $hasComment) {
79+
$commentEntity->setUser($user);
80+
$commentEntity->setPublication($submission);
81+
$commentEntity->setComment($commentText ?? '');
7382

74-
$qualification = (float) $request->get('qualification', null);
83+
if (!empty($filename)) {
84+
$commentEntity->setFile($filename);
85+
}
7586

76-
if (null !== $qualification) {
77-
$submission->setQualification($qualification);
87+
$em->persist($commentEntity);
88+
}
89+
90+
if ($hasQualification) {
91+
$submission->setQualification((float) $qualification);
7892
$submission->setQualificatorId($user->getId());
7993
$submission->setDateOfQualification(new \DateTime());
94+
95+
$em->persist($submission);
8096
}
8197

82-
$em->persist($commentEntity);
8398
$em->flush();
8499

85100
if ($sendMail && $submission->getUser() instanceof User) {

src/CoreBundle/Resources/views/Work/pdf_export.html.twig

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@
2020
<td>{{ submission.user.fullName }}</td>
2121
<td>{{ submission.sentDate|date('Y-m-d H:i') }}</td>
2222
<td>{{ submission.title }}</td>
23-
<td>{{ submission.score ?? '' }}</td>
23+
<td>
24+
{% if submission.qualification is not null %}
25+
{{ submission.qualification }}
26+
{% if submission.publicationParent is defined and submission.publicationParent.qualification is not null %}
27+
/ {{ submission.publicationParent.qualification }}
28+
{% endif %}
29+
{% else %}
30+
{{ 'Not graded yet'|trans }}
31+
{% endif %}
32+
</td>
2433
<td>
2534
{% for comment in submission.comments %}
2635
<p><strong>{{ comment.user.fullName }}</strong>: {{ comment.comment }}</p>

src/CoreBundle/State/CStudentPublicationPostStateProcessor.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ public function process(
6767
}
6868

6969
$payload = $context['request']->toArray();
70+
71+
if (array_key_exists('qualification', $payload)) {
72+
$publication->setQualification((float) $payload['qualification']);
73+
74+
$user = $this->security->getUser();
75+
if ($user instanceof User) {
76+
$publication->setQualificatorId($user->getId());
77+
$publication->setDateOfQualification(new \DateTime());
78+
}
79+
}
80+
7081
if (isset($payload['expiresOn'])) {
7182
$assignment->setExpiresOn(new \DateTime($payload['expiresOn']));
7283
}
@@ -85,11 +96,12 @@ public function process(
8596
$assignment->setEventCalendarId(0);
8697
}
8798

99+
if ($assignment->getIid() !== null) {
100+
$publication->setHasProperties($assignment->getIid());
101+
}
88102
$publication
89-
->setHasProperties($assignment->getIid())
90103
->setViewProperties(true)
91-
->setUser($currentUser)
92-
;
104+
->setUser($currentUser);
93105

94106
$this->entityManager->flush();
95107

src/CourseBundle/Entity/CStudentPublicationComment.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,13 @@ public function getResourceIdentifier(): int|Uuid
141141

142142
public function getResourceName(): string
143143
{
144-
$text = strip_tags($this->getComment());
144+
$comment = trim((string) $this->getComment());
145145

146+
if ($comment === '') {
147+
return 'comment-' . (new \DateTime())->format('Ymd-His');
148+
}
149+
150+
$text = strip_tags($comment);
146151
$text = Slugify::create()->slugify($text);
147152

148153
return substr($text, 0, 40);

0 commit comments

Comments
 (0)