Merged in bugfix/VBV-470-abgabe-aufträge-homogenisierung (pull request #166)

Bugfix/VBV-470 Homogenisierung Abgabe Aufträge

* Make feedback non-checkable without submission

* Submit preparation_assignment on close

# Conflicts:
#	client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue

* Also submit reflection on close

* Fix CourseSessionCompletionData reloading

* User can self toggle LearningContentFeedback completion

* Show submission view for all assignments

* Fix cockpit for condition acceptance

* Close assignments after submission


Approved-by: Dario Aebersold
Approved-by: Daniel Egger
This commit is contained in:
Elia Bieri 2023-08-03 06:32:47 +00:00
parent ed1fce2bd6
commit 667ef96b14
7 changed files with 75 additions and 64 deletions

View File

@ -103,7 +103,7 @@ const sendFeedback = () => {
preparation_task_clarity: preparationTaskClarity, preparation_task_clarity: preparationTaskClarity,
course_negative_feedback: courseNegativeFeedback, course_negative_feedback: courseNegativeFeedback,
course_positive_feedback: coursePositiveFeedback, course_positive_feedback: coursePositiveFeedback,
goald_attainment: goalAttainment, goal_attainment: goalAttainment,
instructor_competence: instructorCompetence, instructor_competence: instructorCompetence,
instructor_respect: instructorRespect, instructor_respect: instructorRespect,
instructor_open_feedback: instructorOpenFeedback, instructor_open_feedback: instructorOpenFeedback,
@ -119,9 +119,8 @@ const sendFeedback = () => {
}); });
log.debug(variables); log.debug(variables);
executeMutation(variables) executeMutation(variables)
.then(({ data, error }) => { .then(({ data }) => {
log.debug(data); log.debug(data);
log.error(error);
mutationResult.value = data; mutationResult.value = data;
}) })
.catch((e) => log.error(e)); .catch((e) => log.error(e));
@ -142,6 +141,7 @@ const sendFeedback = () => {
:start-badge-text="$t('general.introduction')" :start-badge-text="$t('general.introduction')"
:end-badge-text="$t('general.submission')" :end-badge-text="$t('general.submission')"
:base-url="props.page.frontend_url" :base-url="props.page.frontend_url"
close-button-variant="close"
@previous="previousStep()" @previous="previousStep()"
@next="nextStep()" @next="nextStep()"
> >

View File

@ -83,7 +83,9 @@ const totalCount = (status: StatusCount) => {
</div> </div>
<div <div
v-else-if=" v-else-if="
props.learningContentAssignment.assignment_type === 'PREP_ASSIGNMENT' props.learningContentAssignment.assignment_type === 'PREP_ASSIGNMENT' ||
props.learningContentAssignment.assignment_type === 'CONDITION_ACCEPTANCE' ||
props.learningContentAssignment.assignment_type === 'REFLECTION'
" "
> >
{{ {{
@ -93,14 +95,6 @@ const totalCount = (status: StatusCount) => {
}) })
}} }}
</div> </div>
<div v-else-if="props.learningContentAssignment.assignment_type === 'REFLECTION'">
{{
$t("x von y abgeschlossen", {
x: doneCount(state.submissionProgressStatusCount),
y: totalCount(state.submissionProgressStatusCount),
})
}}
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -15,6 +15,7 @@ import type { Dayjs } from "dayjs";
import log from "loglevel"; import log from "loglevel";
import { computed, reactive } from "vue"; import { computed, reactive } from "vue";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
import eventBus from "@/utils/eventBus";
const props = defineProps<{ const props = defineProps<{
assignment: Assignment; assignment: Assignment;
@ -53,6 +54,15 @@ const completionData = computed(() => {
return props.assignmentCompletion?.completion_data ?? {}; return props.assignmentCompletion?.completion_data ?? {};
}); });
const canSubmit = computed(() => {
return (
!state.confirmInput ||
(props.assignment.assignment_type === "CASEWORK" && !state.confirmPerson)
);
});
const isCasework = computed(() => props.assignment.assignment_type === "CASEWORK");
const upsertAssignmentCompletionMutation = useMutation( const upsertAssignmentCompletionMutation = useMutation(
UPSERT_ASSIGNMENT_COMPLETION_MUTATION UPSERT_ASSIGNMENT_COMPLETION_MUTATION
); );
@ -76,6 +86,7 @@ const onSubmit = async () => {
bustItGetCache( bustItGetCache(
`/api/course/completion/${courseSession.value.id}/${useUserStore().id}/` `/api/course/completion/${courseSession.value.id}/${useUserStore().id}/`
); );
eventBus.emit("finishedLearningContent", true);
} catch (error) { } catch (error) {
log.error("Could not submit assignment", error); log.error("Could not submit assignment", error);
} }
@ -98,7 +109,7 @@ const onSubmit = async () => {
data-cy="confirm-submit-results" data-cy="confirm-submit-results"
@toggle="state.confirmInput = !state.confirmInput" @toggle="state.confirmInput = !state.confirmInput"
></ItCheckbox> ></ItCheckbox>
<div class="w-full border-b border-gray-400"> <div v-if="isCasework" class="w-full border-b border-gray-400">
<ItCheckbox <ItCheckbox
class="py-6" class="py-6"
:checkbox-item="{ :checkbox-item="{
@ -121,21 +132,21 @@ const onSubmit = async () => {
</div> </div>
<!-- TODO: find way to find user that will do the corrections --> <!-- TODO: find way to find user that will do the corrections -->
</div> </div>
<div class="flex flex-col space-x-2 pt-6 text-base sm:flex-row"> <div v-if="isCasework" class="flex flex-col space-x-2 pt-6 text-base sm:flex-row">
<p>{{ $t("assignment.assessmentDocumentDisclaimer") }}</p> <p>{{ $t("assignment.assessmentDocumentDisclaimer") }}</p>
<a :href="props.assignment.evaluation_document_url" class="underline"> <a :href="props.assignment.evaluation_document_url" class="underline">
{{ $t("assignment.showAssessmentDocument") }} {{ $t("assignment.showAssessmentDocument") }}
</a> </a>
</div> </div>
<p class="pt-6"> <p v-if="isCasework" class="pt-6">
{{ $t("assignment.dueDateSubmission") }} {{ $t("assignment.dueDateSubmission") }}
<DateEmbedding :single-date="dueDate"></DateEmbedding> <DateEmbedding :single-date="dueDate"></DateEmbedding>
</p> </p>
<ItButton <ItButton
class="mt-6" class="mt-6"
variant="primary" variant="blue"
size="large" size="large"
:disabled="!state.confirmInput || !state.confirmPerson" :disabled="canSubmit"
data-cy="submit-assignment" data-cy="submit-assignment"
@click="onSubmit" @click="onSubmit"
> >
@ -147,7 +158,7 @@ const onSubmit = async () => {
:text="t('assignment.assignmentSubmitted')" :text="t('assignment.assignmentSubmitted')"
data-cy="success-text" data-cy="success-text"
></ItSuccessAlert> ></ItSuccessAlert>
<p class="pt-6"> <p v-if="isCasework" class="pt-6">
{{ {{
$t("assignment.submissionNotificationDisclaimer", { name: circleExpertName }) $t("assignment.submissionNotificationDisclaimer", { name: circleExpertName })
}} }}

View File

@ -12,6 +12,7 @@ import { useUserStore } from "@/stores/user";
import type { import type {
Assignment, Assignment,
AssignmentCompletion, AssignmentCompletion,
AssignmentCompletionStatus,
AssignmentTask, AssignmentTask,
CourseSessionAssignment, CourseSessionAssignment,
CourseSessionUser, CourseSessionUser,
@ -23,6 +24,7 @@ import dayjs from "dayjs";
import * as log from "loglevel"; import * as log from "loglevel";
import { computed, onMounted, reactive } from "vue"; import { computed, onMounted, reactive } from "vue";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
import { bustItGetCache } from "@/fetchHelpers";
import { learningContentTypeData } from "@/utils/typeMaps"; import { learningContentTypeData } from "@/utils/typeMaps";
const { t } = useTranslation(); const { t } = useTranslation();
@ -87,16 +89,7 @@ onMounted(async () => {
// create initial `AssignmentCompletion` first, so that it exists and we don't // create initial `AssignmentCompletion` first, so that it exists and we don't
// have reactivity problem accessing it. // have reactivity problem accessing it.
await upsertAssignmentCompletionMutation.executeMutation({ await upsertAssignmentCompletion("IN_PROGRESS");
assignmentId: props.learningContent.content_assignment_id.toString(),
courseSessionId: courseSession.value.id.toString(),
learningContentId: props.learningContent.id.toString(),
completionDataString: JSON.stringify({}),
completionStatus: "IN_PROGRESS",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
id: assignmentCompletion.value?.id,
});
queryResult.resume(); queryResult.resume();
try { try {
@ -113,16 +106,10 @@ onMounted(async () => {
const numTasks = computed(() => assignment.value?.tasks?.length ?? 0); const numTasks = computed(() => assignment.value?.tasks?.length ?? 0);
const numPages = computed(() => { const numPages = computed(() => {
if (assignmentType.value === "CASEWORK") { return numTasks.value + 2;
// casework has extra submission page
return numTasks.value + 2;
}
return numTasks.value + 1;
}); });
const showPreviousButton = computed(() => stepIndex.value != 0); const showPreviousButton = computed(() => stepIndex.value != 0);
const showNextButton = computed(() => stepIndex.value + 1 < numPages.value); const showNextButton = computed(() => stepIndex.value + 1 < numPages.value);
const showExitButton = computed(() => numPages.value === stepIndex.value + 1);
const dueDate = computed(() => const dueDate = computed(() =>
dayjs(state.courseSessionAssignment?.submission_deadline_start) dayjs(state.courseSessionAssignment?.submission_deadline_start)
); );
@ -133,6 +120,26 @@ const currentTask = computed(() => {
return undefined; return undefined;
}); });
const upsertAssignmentCompletion = async (status: AssignmentCompletionStatus) => {
try {
await upsertAssignmentCompletionMutation.executeMutation({
assignmentId: props.learningContent.content_assignment_id.toString(),
courseSessionId: courseSession.value.id.toString(),
learningContentId: props.learningContent.id.toString(),
completionDataString: JSON.stringify({}),
completionStatus: status,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
id: assignmentCompletion.value?.id,
});
bustItGetCache(
`/api/course/completion/${courseSession.value.id}/${useUserStore().id}/`
);
} catch (error) {
log.error("Could not submit assignment", error);
}
};
const handleBack = () => { const handleBack = () => {
log.debug("handleBack"); log.debug("handleBack");
if (stepIndex.value > 0) { if (stepIndex.value > 0) {
@ -161,19 +168,12 @@ const jumpToTask = (task: AssignmentTask) => {
const getTitle = () => { const getTitle = () => {
if (0 === stepIndex.value) { if (0 === stepIndex.value) {
return t("general.introduction"); return t("general.introduction");
} else if ( } else if (stepIndex.value === numPages.value - 1) {
assignmentType.value === "CASEWORK" &&
stepIndex.value === numPages.value - 1
) {
return t("general.submission"); return t("general.submission");
} }
return currentTask?.value?.value.title ?? "Unknown"; return currentTask?.value?.value.title ?? "Unknown";
}; };
const assignmentType = computed(() => {
return assignment.value?.assignment_type ?? "CASEWORK";
});
const subTitle = computed(() => { const subTitle = computed(() => {
if (assignment.value) { if (assignment.value) {
const prefix = learningContentTypeData(props.learningContent).title; const prefix = learningContentTypeData(props.learningContent).title;
@ -187,19 +187,6 @@ const assignmentUser = computed(() => {
(user) => user.user_id === userStore.id (user) => user.user_id === userStore.id
) as CourseSessionUser; ) as CourseSessionUser;
}); });
const endBadgeText = computed(() => {
if (assignmentType.value === "PREP_ASSIGNMENT") {
return t("Aufgaben");
} else if (assignmentType.value === "CASEWORK") {
return t("Abgabe");
} else if (assignmentType.value === "CONDITION_ACCEPTANCE") {
return t("Akzeptieren");
}
// just return the number of tasks as default
return (assignment.value?.tasks.length ?? 0).toString();
});
</script> </script>
<template> <template>
@ -215,13 +202,13 @@ const endBadgeText = computed(() => {
:learning-content="props.learningContent" :learning-content="props.learningContent"
:steps-count="numPages" :steps-count="numPages"
:show-next-button="showNextButton && stepIndex !== 0" :show-next-button="showNextButton && stepIndex !== 0"
:show-exit-button="showExitButton" :show-exit-button="false"
:show-start-button="showNextButton && stepIndex === 0" :show-start-button="showNextButton && stepIndex === 0"
:show-previous-button="showPreviousButton" :show-previous-button="showPreviousButton"
:base-url="props.learningContent.frontend_url" :base-url="props.learningContent.frontend_url"
step-query-param="step" step-query-param="step"
start-badge-text="Einleitung" start-badge-text="Einleitung"
:end-badge-text="endBadgeText" :end-badge-text="$t('Abgabe')"
close-button-variant="close" close-button-variant="close"
@previous="handleBack()" @previous="handleBack()"
@next="handleContinue()" @next="handleContinue()"
@ -241,7 +228,7 @@ const endBadgeText = computed(() => {
:learning-content-id="props.learningContent.id" :learning-content-id="props.learningContent.id"
></AssignmentTaskView> ></AssignmentTaskView>
<AssignmentSubmissionView <AssignmentSubmissionView
v-else-if="assignmentType === 'CASEWORK' && stepIndex + 1 === numPages" v-else-if="stepIndex + 1 === numPages"
:due-date="dueDate" :due-date="dueDate"
:assignment="assignment" :assignment="assignment"
:assignment-completion="assignmentCompletion" :assignment-completion="assignmentCompletion"

View File

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import eventBus from "@/utils/eventBus";
import { computed } from "vue"; import { computed } from "vue";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
@ -15,6 +14,8 @@ const props = defineProps<{
const { t } = useTranslation(); const { t } = useTranslation();
const emit = defineEmits(["start", "previous", "next", "exit"]);
// eslint-disable-next-line vue/return-in-computed-property // eslint-disable-next-line vue/return-in-computed-property
const closingButtonText = computed(() => { const closingButtonText = computed(() => {
switch (props.closingButtonVariant) { switch (props.closingButtonVariant) {
@ -24,8 +25,6 @@ const closingButtonText = computed(() => {
return t("learningContent.markAsDone"); return t("learningContent.markAsDone");
} }
}); });
defineEmits(["start", "previous", "next"]);
</script> </script>
<template> <template>
@ -64,7 +63,7 @@ defineEmits(["start", "previous", "next"]);
type="button" type="button"
class="btn-blue z-10 flex items-center" class="btn-blue z-10 flex items-center"
data-cy="complete-and-continue" data-cy="complete-and-continue"
@click="eventBus.emit('finishedLearningContent', true)" @click="emit('exit')"
> >
{{ closingButtonText }} {{ closingButtonText }}
<it-icon-check <it-icon-check

View File

@ -6,6 +6,7 @@ import LearningContentFooter from "@/pages/learningPath/learningContentPage/layo
import type { LearningContent } from "@/types"; import type { LearningContent } from "@/types";
import { learningContentTypeData } from "@/utils/typeMaps"; import { learningContentTypeData } from "@/utils/typeMaps";
import { computed } from "vue"; import { computed } from "vue";
import eventBus from "@/utils/eventBus";
interface Props { interface Props {
title?: string; title?: string;
@ -23,6 +24,7 @@ interface Props {
closeButtonVariant?: ClosingButtonVariant; closeButtonVariant?: ClosingButtonVariant;
baseUrl?: string; baseUrl?: string;
stepQueryParam?: string; stepQueryParam?: string;
beforeExitCallback?: () => Promise<void>;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -35,6 +37,7 @@ const props = withDefaults(defineProps<Props>(), {
closeButtonVariant: "mark_as_done", closeButtonVariant: "mark_as_done",
baseUrl: undefined, baseUrl: undefined,
stepQueryParam: undefined, stepQueryParam: undefined,
beforeExitCallback: async () => Promise.resolve(),
}); });
const subTitle = computed(() => { const subTitle = computed(() => {
@ -57,6 +60,11 @@ const icon = computed(() => {
return ""; return "";
}); });
const onExit = async () => {
await props.beforeExitCallback();
eventBus.emit("finishedLearningContent", true);
};
const emit = defineEmits(["previous", "next", "exit"]); const emit = defineEmits(["previous", "next", "exit"]);
</script> </script>
@ -98,5 +106,6 @@ const emit = defineEmits(["previous", "next", "exit"]);
@previous="emit('previous')" @previous="emit('previous')"
@next="emit('next')" @next="emit('next')"
@start="emit('next')" @start="emit('next')"
@exit="onExit"
></LearningContentFooter> ></LearningContentFooter>
</template> </template>

View File

@ -4,6 +4,7 @@ import LearningContentFooter from "@/pages/learningPath/learningContentPage/layo
import type { LearningContent } from "@/types"; import type { LearningContent } from "@/types";
import { learningContentTypeData } from "@/utils/typeMaps"; import { learningContentTypeData } from "@/utils/typeMaps";
import { computed } from "vue"; import { computed } from "vue";
import eventBus from "@/utils/eventBus";
export interface Props { export interface Props {
title?: string; title?: string;
@ -38,6 +39,15 @@ const icon = computed(() => {
} }
return ""; return "";
}); });
const closingButtonVariant = computed(() => {
if (props.learningContent) {
return props.learningContent.can_user_self_toggle_course_completion
? "mark_as_done"
: "close";
}
return "close";
});
</script> </script>
<template> <template>
@ -62,6 +72,7 @@ const icon = computed(() => {
:show-previous-button="false" :show-previous-button="false"
:show-exit-button="true" :show-exit-button="true"
:show-start-button="false" :show-start-button="false"
:closing-button-variant="'close'" :closing-button-variant="closingButtonVariant"
@exit="eventBus.emit('finishedLearningContent', true)"
></LearningContentFooter> ></LearningContentFooter>
</template> </template>