Skip to content

Assignment: Implement assignment tool with vue and symfony style - refs BT#22568 #6257

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

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
15 changes: 3 additions & 12 deletions assets/vue/components/assignments/AssignmentsForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
:label="t('Assignment name')"
/>

<BaseInputText
<BaseTinyEditor
v-model="assignment.description"
:label="t('Description')"
editor-id=""
/>

<BaseAdvancedSettingsButton v-model="showAdvancedSettings">
Expand Down Expand Up @@ -105,12 +106,6 @@
</BaseAdvancedSettingsButton>

<div class="flex justify-end space-x-2 mt-4">
<BaseButton
:label="t('Back')"
icon="arrow-left"
type="white"
@click="goBack"
/>
<BaseButton
:disabled="isFormLoading"
:label="t('Save')"
Expand All @@ -137,6 +132,7 @@ import { useI18n } from "vue-i18n"
import { useCidReq } from "../../composables/cidReq"
import { useRoute, useRouter } from "vue-router"
import { RESOURCE_LINK_PUBLISHED } from "../../constants/entity/resourcelink"
import BaseTinyEditor from "../basecomponents/BaseTinyEditor.vue"

const props = defineProps({
defaultAssignment: {
Expand Down Expand Up @@ -198,7 +194,6 @@ watchEffect(() => {

if (defaultAssignment.weight > 0) {
chkAddToGradebook.value = true
//assignment.gradebookId.id = defaultAssignment.gradebookCategoryId
assignment.weight = defaultAssignment.weight
}

Expand Down Expand Up @@ -317,8 +312,4 @@ const onSubmit = async () => {

emit("submit", publicationStudent)
}

function goBack() {
router.push({ name: "AssignmentsList", query: { cid, sid, gid } })
}
</script>
189 changes: 189 additions & 0 deletions assets/vue/components/assignments/CorrectAndRateModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<template>
<Dialog
v-model:visible="visible"
modal
:header="t('Comments')"
:style="{ width: '600px' }"
@hide="onHide"
>
<div class="space-y-4">
<div class="bg-gray-100 p-3 rounded">
<h4 class="font-bold text-md">{{ props.item.title }}</h4>
<div
class="text-sm text-gray-700 prose max-w-none"
v-html="props.item.description"
></div>
</div>

<Textarea
v-model="comment"
:placeholder="t('Write your comment...')"
class="w-full"
rows="5"
/>

<div class="flex flex-col gap-2">
<label>{{ t("Attach file (optional)") }}</label>
<input
type="file"
@change="handleFileUpload"
/>
</div>

<div class="flex items-center gap-2">
<BaseCheckbox
id="sendmail"
v-model="sendMail"
:label="t('Send mail to student')"
name=""
/>
</div>

<div class="flex justify-end gap-2">
<Button
:label="t('Cancel')"
class="p-button-text"
@click="close"
/>
<Button
:label="t('Send')"
@click="submit"
/>
</div>
</div>
<div
v-if="comments.length"
class="mt-6 border-t pt-4 space-y-4 max-h-[300px] overflow-auto"
>
<div
v-for="comment in comments"
:key="comment['@id']"
class="bg-gray-10 border rounded p-3 space-y-2"
>
<div class="flex justify-between items-center">
<span class="font-semibold text-sm">
{{ comment.user?.fullName || comment.user?.fullname || "Unknown User" }}
</span>
<span class="text-gray-50 text-xs">
{{ formatDate(comment.sentAt) }}
</span>
</div>
<p class="text-gray-90 whitespace-pre-line text-sm">
{{ comment.comment }}
</p>
<div
v-if="comment.file && comment.downloadUrl"
class="flex items-center gap-1 text-sm"
>
<i class="pi pi-paperclip text-gray-50"></i>
<a
:href="comment.downloadUrl"
target="_blank"
class="text-blue-50 underline break-all"
>
{{ comment.file }}
</a>
</div>
</div>
</div>
</Dialog>
</template>

<script setup>
import { ref, watch } from "vue"
import { useNotification } from "../../composables/notification"
import { useI18n } from "vue-i18n"
import Textarea from "primevue/textarea"
import Button from "primevue/button"
import Dialog from "primevue/dialog"
import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"
import cStudentPublicationService from "../../services/cstudentpublication"

const props = defineProps({
modelValue: Boolean,
item: Object,
})

const emit = defineEmits(["update:modelValue", "commentSent"])

const visible = ref(false)
const comment = ref("")
const sendMail = ref(false)
const selectedFile = ref(null)
const { t } = useI18n()
const notification = useNotification()

watch(
() => props.modelValue,
async (newVal) => {
visible.value = newVal
if (newVal) {
comment.value = ""
sendMail.value = false
selectedFile.value = null
comments.value = await cStudentPublicationService.loadComments(props.item.iid)
}
},
)

const comments = ref([])

function formatDate(dateStr) {
if (!dateStr) return ""
const now = new Date()
const date = new Date(dateStr)
const diffMs = date - now
const diffMinutes = Math.round(diffMs / 60000)

const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" })
return rtf.format(diffMinutes, "minute")
}

function onHide() {
emit("update:modelValue", false)
}

function close() {
emit("update:modelValue", false)
}

function handleFileUpload(event) {
selectedFile.value = event.target.files[0] || null
}

async function submit() {
if (!comment.value.trim()) {
notification.showErrorNotification(t("Comment is required"))
return
}

try {
const formData = new FormData()
if (selectedFile.value) {
formData.append("uploadFile", selectedFile.value)
}
formData.append("comment", comment.value)

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

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

comments.value = await cStudentPublicationService.loadComments(props.item.iid)
comment.value = ""
selectedFile.value = null
} catch (error) {
notification.showErrorNotification(error)
}
}

function getResourceNodeId(resourceNode) {
if (!resourceNode) return 0
const match = resourceNode.match(/\/(\d+)$/)
return match ? parseInt(match[1], 10) : 0
}
</script>
119 changes: 119 additions & 0 deletions assets/vue/components/assignments/EditStudentSubmissionForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<template>
<Dialog
v-model:visible="visible"
modal
:header="t('Edit Submission')"
:style="{ width: '500px' }"
@hide="onHide"
>
<div class="space-y-4">
<InputText
v-model="form.title"
:placeholder="t('Title')"
class="w-full"
/>

<div class="flex items-center gap-2">
<a
v-if="props.item && props.item.downloadUrl"
:href="props.item.downloadUrl"
class="btn btn--primary"
target="_self"
>
<BaseIcon icon="download" />
{{ t("Download file") }}
</a>
</div>

<Textarea
v-model="form.description"
:placeholder="t('Description')"
rows="5"
class="w-full"
/>

<div class="flex items-center gap-2">
<BaseCheckbox
id="senemail"
v-model="form.sendMail"
:label="t('Send mail to student')"
name="senemail"
/>
</div>

<div class="flex justify-end gap-2">
<Button
:label="t('Cancel')"
class="p-button-text"
@click="cancel"
/>
<Button
:label="t('Update')"
@click="submit"
/>
</div>
</div>
</Dialog>
</template>

<script setup>
import { ref, watch } from "vue"
import { useNotification } from "../../composables/notification"
import cStudentPublicationService from "../../services/cstudentpublication"
import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"
import { useI18n } from "vue-i18n"
import Textarea from "primevue/textarea"
import BaseIcon from "../basecomponents/BaseIcon.vue"

const props = defineProps({
modelValue: Boolean,
item: Object,
})

const emit = defineEmits(["update:modelValue", "updated"])

const visible = ref(false)
const form = ref({
title: "",
description: "",
sendMail: false,
})

const { t } = useI18n()
const notification = useNotification()

watch(
() => props.modelValue,
(newVal) => {
visible.value = newVal
if (newVal && props.item) {
form.value.title = props.item.title || ""
form.value.description = props.item.description || ""
form.value.sendMail = false
}
},
)

function cancel() {
emit("update:modelValue", false)
}

function onHide() {
emit("update:modelValue", false)
}

async function submit() {
try {
await cStudentPublicationService.updateSubmission(props.item.iid, {
title: form.value.title,
description: form.value.description,
sendMail: form.value.sendMail,
})
notification.showSuccessNotification(t("Submission updated!"))
emit("updated")
emit("update:modelValue", false)
} catch (error) {
notification.showErrorNotification(error)
}
}
</script>
Loading
Loading