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:
parent
ed1fce2bd6
commit
667ef96b14
|
|
@ -103,7 +103,7 @@ const sendFeedback = () => {
|
|||
preparation_task_clarity: preparationTaskClarity,
|
||||
course_negative_feedback: courseNegativeFeedback,
|
||||
course_positive_feedback: coursePositiveFeedback,
|
||||
goald_attainment: goalAttainment,
|
||||
goal_attainment: goalAttainment,
|
||||
instructor_competence: instructorCompetence,
|
||||
instructor_respect: instructorRespect,
|
||||
instructor_open_feedback: instructorOpenFeedback,
|
||||
|
|
@ -119,9 +119,8 @@ const sendFeedback = () => {
|
|||
});
|
||||
log.debug(variables);
|
||||
executeMutation(variables)
|
||||
.then(({ data, error }) => {
|
||||
.then(({ data }) => {
|
||||
log.debug(data);
|
||||
log.error(error);
|
||||
mutationResult.value = data;
|
||||
})
|
||||
.catch((e) => log.error(e));
|
||||
|
|
@ -142,6 +141,7 @@ const sendFeedback = () => {
|
|||
:start-badge-text="$t('general.introduction')"
|
||||
:end-badge-text="$t('general.submission')"
|
||||
:base-url="props.page.frontend_url"
|
||||
close-button-variant="close"
|
||||
@previous="previousStep()"
|
||||
@next="nextStep()"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,9 @@ const totalCount = (status: StatusCount) => {
|
|||
</div>
|
||||
<div
|
||||
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 v-else-if="props.learningContentAssignment.assignment_type === 'REFLECTION'">
|
||||
{{
|
||||
$t("x von y abgeschlossen", {
|
||||
x: doneCount(state.submissionProgressStatusCount),
|
||||
y: totalCount(state.submissionProgressStatusCount),
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import type { Dayjs } from "dayjs";
|
|||
import log from "loglevel";
|
||||
import { computed, reactive } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import eventBus from "@/utils/eventBus";
|
||||
|
||||
const props = defineProps<{
|
||||
assignment: Assignment;
|
||||
|
|
@ -53,6 +54,15 @@ const completionData = computed(() => {
|
|||
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(
|
||||
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
|
||||
);
|
||||
|
|
@ -76,6 +86,7 @@ const onSubmit = async () => {
|
|||
bustItGetCache(
|
||||
`/api/course/completion/${courseSession.value.id}/${useUserStore().id}/`
|
||||
);
|
||||
eventBus.emit("finishedLearningContent", true);
|
||||
} catch (error) {
|
||||
log.error("Could not submit assignment", error);
|
||||
}
|
||||
|
|
@ -98,7 +109,7 @@ const onSubmit = async () => {
|
|||
data-cy="confirm-submit-results"
|
||||
@toggle="state.confirmInput = !state.confirmInput"
|
||||
></ItCheckbox>
|
||||
<div class="w-full border-b border-gray-400">
|
||||
<div v-if="isCasework" class="w-full border-b border-gray-400">
|
||||
<ItCheckbox
|
||||
class="py-6"
|
||||
:checkbox-item="{
|
||||
|
|
@ -121,21 +132,21 @@ const onSubmit = async () => {
|
|||
</div>
|
||||
<!-- TODO: find way to find user that will do the corrections -->
|
||||
</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>
|
||||
<a :href="props.assignment.evaluation_document_url" class="underline">
|
||||
{{ $t("assignment.showAssessmentDocument") }}
|
||||
</a>
|
||||
</div>
|
||||
<p class="pt-6">
|
||||
<p v-if="isCasework" class="pt-6">
|
||||
{{ $t("assignment.dueDateSubmission") }}
|
||||
<DateEmbedding :single-date="dueDate"></DateEmbedding>
|
||||
</p>
|
||||
<ItButton
|
||||
class="mt-6"
|
||||
variant="primary"
|
||||
variant="blue"
|
||||
size="large"
|
||||
:disabled="!state.confirmInput || !state.confirmPerson"
|
||||
:disabled="canSubmit"
|
||||
data-cy="submit-assignment"
|
||||
@click="onSubmit"
|
||||
>
|
||||
|
|
@ -147,7 +158,7 @@ const onSubmit = async () => {
|
|||
:text="t('assignment.assignmentSubmitted')"
|
||||
data-cy="success-text"
|
||||
></ItSuccessAlert>
|
||||
<p class="pt-6">
|
||||
<p v-if="isCasework" class="pt-6">
|
||||
{{
|
||||
$t("assignment.submissionNotificationDisclaimer", { name: circleExpertName })
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { useUserStore } from "@/stores/user";
|
|||
import type {
|
||||
Assignment,
|
||||
AssignmentCompletion,
|
||||
AssignmentCompletionStatus,
|
||||
AssignmentTask,
|
||||
CourseSessionAssignment,
|
||||
CourseSessionUser,
|
||||
|
|
@ -23,6 +24,7 @@ import dayjs from "dayjs";
|
|||
import * as log from "loglevel";
|
||||
import { computed, onMounted, reactive } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
import { bustItGetCache } from "@/fetchHelpers";
|
||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -87,16 +89,7 @@ onMounted(async () => {
|
|||
|
||||
// create initial `AssignmentCompletion` first, so that it exists and we don't
|
||||
// have reactivity problem accessing it.
|
||||
await upsertAssignmentCompletionMutation.executeMutation({
|
||||
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,
|
||||
});
|
||||
await upsertAssignmentCompletion("IN_PROGRESS");
|
||||
queryResult.resume();
|
||||
|
||||
try {
|
||||
|
|
@ -113,16 +106,10 @@ onMounted(async () => {
|
|||
|
||||
const numTasks = computed(() => assignment.value?.tasks?.length ?? 0);
|
||||
const numPages = computed(() => {
|
||||
if (assignmentType.value === "CASEWORK") {
|
||||
// casework has extra submission page
|
||||
return numTasks.value + 2;
|
||||
}
|
||||
|
||||
return numTasks.value + 1;
|
||||
return numTasks.value + 2;
|
||||
});
|
||||
const showPreviousButton = computed(() => stepIndex.value != 0);
|
||||
const showNextButton = computed(() => stepIndex.value + 1 < numPages.value);
|
||||
const showExitButton = computed(() => numPages.value === stepIndex.value + 1);
|
||||
const dueDate = computed(() =>
|
||||
dayjs(state.courseSessionAssignment?.submission_deadline_start)
|
||||
);
|
||||
|
|
@ -133,6 +120,26 @@ const currentTask = computed(() => {
|
|||
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 = () => {
|
||||
log.debug("handleBack");
|
||||
if (stepIndex.value > 0) {
|
||||
|
|
@ -161,19 +168,12 @@ const jumpToTask = (task: AssignmentTask) => {
|
|||
const getTitle = () => {
|
||||
if (0 === stepIndex.value) {
|
||||
return t("general.introduction");
|
||||
} else if (
|
||||
assignmentType.value === "CASEWORK" &&
|
||||
stepIndex.value === numPages.value - 1
|
||||
) {
|
||||
} else if (stepIndex.value === numPages.value - 1) {
|
||||
return t("general.submission");
|
||||
}
|
||||
return currentTask?.value?.value.title ?? "Unknown";
|
||||
};
|
||||
|
||||
const assignmentType = computed(() => {
|
||||
return assignment.value?.assignment_type ?? "CASEWORK";
|
||||
});
|
||||
|
||||
const subTitle = computed(() => {
|
||||
if (assignment.value) {
|
||||
const prefix = learningContentTypeData(props.learningContent).title;
|
||||
|
|
@ -187,19 +187,6 @@ const assignmentUser = computed(() => {
|
|||
(user) => user.user_id === userStore.id
|
||||
) 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>
|
||||
|
||||
<template>
|
||||
|
|
@ -215,13 +202,13 @@ const endBadgeText = computed(() => {
|
|||
:learning-content="props.learningContent"
|
||||
:steps-count="numPages"
|
||||
:show-next-button="showNextButton && stepIndex !== 0"
|
||||
:show-exit-button="showExitButton"
|
||||
:show-exit-button="false"
|
||||
:show-start-button="showNextButton && stepIndex === 0"
|
||||
:show-previous-button="showPreviousButton"
|
||||
:base-url="props.learningContent.frontend_url"
|
||||
step-query-param="step"
|
||||
start-badge-text="Einleitung"
|
||||
:end-badge-text="endBadgeText"
|
||||
:end-badge-text="$t('Abgabe')"
|
||||
close-button-variant="close"
|
||||
@previous="handleBack()"
|
||||
@next="handleContinue()"
|
||||
|
|
@ -241,7 +228,7 @@ const endBadgeText = computed(() => {
|
|||
:learning-content-id="props.learningContent.id"
|
||||
></AssignmentTaskView>
|
||||
<AssignmentSubmissionView
|
||||
v-else-if="assignmentType === 'CASEWORK' && stepIndex + 1 === numPages"
|
||||
v-else-if="stepIndex + 1 === numPages"
|
||||
:due-date="dueDate"
|
||||
:assignment="assignment"
|
||||
:assignment-completion="assignmentCompletion"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
import eventBus from "@/utils/eventBus";
|
||||
import { computed } from "vue";
|
||||
import { useTranslation } from "i18next-vue";
|
||||
|
||||
|
|
@ -15,6 +14,8 @@ const props = defineProps<{
|
|||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const emit = defineEmits(["start", "previous", "next", "exit"]);
|
||||
|
||||
// eslint-disable-next-line vue/return-in-computed-property
|
||||
const closingButtonText = computed(() => {
|
||||
switch (props.closingButtonVariant) {
|
||||
|
|
@ -24,8 +25,6 @@ const closingButtonText = computed(() => {
|
|||
return t("learningContent.markAsDone");
|
||||
}
|
||||
});
|
||||
|
||||
defineEmits(["start", "previous", "next"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -64,7 +63,7 @@ defineEmits(["start", "previous", "next"]);
|
|||
type="button"
|
||||
class="btn-blue z-10 flex items-center"
|
||||
data-cy="complete-and-continue"
|
||||
@click="eventBus.emit('finishedLearningContent', true)"
|
||||
@click="emit('exit')"
|
||||
>
|
||||
{{ closingButtonText }}
|
||||
<it-icon-check
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import LearningContentFooter from "@/pages/learningPath/learningContentPage/layo
|
|||
import type { LearningContent } from "@/types";
|
||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||
import { computed } from "vue";
|
||||
import eventBus from "@/utils/eventBus";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
|
|
@ -23,6 +24,7 @@ interface Props {
|
|||
closeButtonVariant?: ClosingButtonVariant;
|
||||
baseUrl?: string;
|
||||
stepQueryParam?: string;
|
||||
beforeExitCallback?: () => Promise<void>;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
|
@ -35,6 +37,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
closeButtonVariant: "mark_as_done",
|
||||
baseUrl: undefined,
|
||||
stepQueryParam: undefined,
|
||||
beforeExitCallback: async () => Promise.resolve(),
|
||||
});
|
||||
|
||||
const subTitle = computed(() => {
|
||||
|
|
@ -57,6 +60,11 @@ const icon = computed(() => {
|
|||
return "";
|
||||
});
|
||||
|
||||
const onExit = async () => {
|
||||
await props.beforeExitCallback();
|
||||
eventBus.emit("finishedLearningContent", true);
|
||||
};
|
||||
|
||||
const emit = defineEmits(["previous", "next", "exit"]);
|
||||
</script>
|
||||
|
||||
|
|
@ -98,5 +106,6 @@ const emit = defineEmits(["previous", "next", "exit"]);
|
|||
@previous="emit('previous')"
|
||||
@next="emit('next')"
|
||||
@start="emit('next')"
|
||||
@exit="onExit"
|
||||
></LearningContentFooter>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import LearningContentFooter from "@/pages/learningPath/learningContentPage/layo
|
|||
import type { LearningContent } from "@/types";
|
||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||
import { computed } from "vue";
|
||||
import eventBus from "@/utils/eventBus";
|
||||
|
||||
export interface Props {
|
||||
title?: string;
|
||||
|
|
@ -38,6 +39,15 @@ const icon = computed(() => {
|
|||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
const closingButtonVariant = computed(() => {
|
||||
if (props.learningContent) {
|
||||
return props.learningContent.can_user_self_toggle_course_completion
|
||||
? "mark_as_done"
|
||||
: "close";
|
||||
}
|
||||
return "close";
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -62,6 +72,7 @@ const icon = computed(() => {
|
|||
:show-previous-button="false"
|
||||
:show-exit-button="true"
|
||||
:show-start-button="false"
|
||||
:closing-button-variant="'close'"
|
||||
:closing-button-variant="closingButtonVariant"
|
||||
@exit="eventBus.emit('finishedLearningContent', true)"
|
||||
></LearningContentFooter>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Reference in New Issue