Merged in feature/composable-course-session (pull request #85)

Feature/composable course session

Approved-by: Elia Bieri
This commit is contained in:
Daniel Egger 2023-05-19 08:16:44 +00:00
commit 3acae49217
22 changed files with 132 additions and 157 deletions

View File

@ -30,8 +30,10 @@ onMounted(() => {
log.debug("App mounted");
eventBus.on("switchedCourseSession", () => {
// FIXME: clean up with VBV-305
// Rerender the component tree, when the course session gets switched.
// So that the current learning path completion data gets updated.
componentKey.value++;
log.info("Switched course session, re-evaluate component tree");
});
});
</script>

View File

@ -0,0 +1,30 @@
import { useCourseSessionsStore } from "@/stores/courseSessions";
import type { CourseSession } from "@/types";
import log from "loglevel";
import type { ComputedRef } from "vue";
import { computed } from "vue";
export function useCurrentCourseSession() {
/**
* We often need the current course session in our components.
* With this composable we can get it easily.
*/
const store = useCourseSessionsStore();
const result: ComputedRef<CourseSession> = computed(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
() => {
if (!store.currentCourseSession) {
log.error(
"currentCourseSession is only defined in pages with :courseSlug in the route"
);
throw new Error(
`currentCourseSession is not defined in the store.
It is only defined in pages with :courseSlug in the route`
);
}
return store.currentCourseSession;
}
);
return result;
}

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { useCurrentCourseSession } from "@/composables";
import { useCockpitStore } from "@/stores/cockpit";
import { useCompetenceStore } from "@/stores/competence";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useLearningPathStore } from "@/stores/learningPath";
import { useUserStore } from "@/stores/user";
import * as log from "loglevel";
@ -16,25 +16,22 @@ const props = defineProps<{
const cockpitStore = useCockpitStore();
const competenceStore = useCompetenceStore();
const learningPathStore = useLearningPathStore();
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
onMounted(async () => {
log.debug("CockpitParentPage mounted", props.courseSlug);
try {
const currentCourseSession = courseSessionsStore.currentCourseSession;
if (currentCourseSession?.id) {
await cockpitStore.loadCourseSessionUsers(currentCourseSession.id);
cockpitStore.courseSessionUsers?.forEach((csu) => {
competenceStore.loadCompetenceProfilePage(
props.courseSlug + "-competence",
csu.user_id
);
await cockpitStore.loadCourseSessionUsers(courseSession.value.id);
cockpitStore.courseSessionUsers?.forEach((csu) => {
competenceStore.loadCompetenceProfilePage(
props.courseSlug + "-competence",
csu.user_id
);
learningPathStore.loadLearningPath(props.courseSlug + "-lp", csu.user_id);
});
learningPathStore.loadLearningPath(props.courseSlug + "-lp", useUserStore().id);
}
learningPathStore.loadLearningPath(props.courseSlug + "-lp", csu.user_id);
});
learningPathStore.loadLearningPath(props.courseSlug + "-lp", useUserStore().id);
} catch (error) {
log.error(error);
}

View File

@ -59,8 +59,8 @@ import HorizontalBarChart from "@/components/ui/HorizontalBarChart.vue";
import OpenFeedback from "@/components/ui/OpenFeedback.vue";
import RatingScale from "@/components/ui/RatingScale.vue";
import VerticalBarChart from "@/components/ui/VerticalBarChart.vue";
import { useCurrentCourseSession } from "@/composables";
import { itGet } from "@/fetchHelpers";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import * as log from "loglevel";
import { onMounted, reactive } from "vue";
import { useI18n } from "vue-i18n";
@ -79,7 +79,7 @@ const props = defineProps<{
log.debug("FeedbackPage created", props.circleId);
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
const { t } = useI18n();
const orderedQuestions = [
@ -144,7 +144,7 @@ const feedbackData = reactive<FeedbackData>({ amount: 0, questions: {} });
onMounted(async () => {
log.debug("FeedbackPage mounted");
const data = await itGet(
`/api/core/feedback/${courseSessionsStore.currentCourseSession?.course.id}/${props.circleId}`
`/api/core/feedback/${courseSession.value.course.id}/${props.circleId}`
);
Object.assign(feedbackData, data);
});

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import { useCurrentCourseSession } from "@/composables";
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
import EvaluationContainer from "@/pages/cockpit/assignmentEvaluationPage/EvaluationContainer.vue";
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import type {
Assignment,
AssignmentCompletion,
@ -32,14 +32,14 @@ const state: StateInterface = reactive({
assignmentUser: undefined,
});
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
const router = useRouter();
// noinspection TypeScriptValidateTypes TODO: because of IntelliJ
const queryResult = useQuery({
query: ASSIGNMENT_COMPLETION_QUERY,
variables: {
courseSessionId: courseSessionsStore.currentCourseSession!.id.toString(),
courseSessionId: courseSession.value.id.toString(),
assignmentId: props.assignmentId,
assignmentUserId: props.userId,
},
@ -48,11 +48,9 @@ const queryResult = useQuery({
onMounted(async () => {
log.debug("AssignmentView mounted", props.assignmentId, props.userId);
if (courseSessionsStore.currentCourseSession) {
state.assignmentUser = courseSessionsStore.currentCourseSession.users.find(
(user) => user.user_id === Number(props.userId)
);
}
state.assignmentUser = courseSession.value.users.find(
(user) => user.user_id === Number(props.userId)
);
});
function close() {

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { useCurrentCourseSession } from "@/composables";
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import type { Assignment, AssignmentCompletion, CourseSessionUser } from "@/types";
import { useMutation } from "@urql/vue";
import dayjs, { Dayjs } from "dayjs";
@ -17,7 +17,7 @@ const emit = defineEmits(["startEvaluation"]);
log.debug("EvaluationIntro setup");
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
const upsertAssignmentCompletionMutation = useMutation(
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
@ -29,7 +29,7 @@ async function startEvaluation() {
// noinspection TypeScriptValidateTypes
upsertAssignmentCompletionMutation.executeMutation({
assignmentId: props.assignment.id.toString(),
courseSessionId: (courseSessionsStore?.currentCourseSession?.id ?? 0).toString(),
courseSessionId: courseSession.value.id.toString(),
assignmentUserId: props.assignmentUser.user_id.toString(),
completionStatus: "EVALUATION_IN_PROGRESS",
completionDataString: JSON.stringify({}),

View File

@ -1,12 +1,12 @@
<script setup lang="ts">
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
import { useCurrentCourseSession } from "@/composables";
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
import {
maxAssignmentPoints,
pointsToGrade,
userAssignmentPoints,
} from "@/services/assignmentService";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import type {
Assignment,
AssignmentCompletion,
@ -34,7 +34,7 @@ const state = reactive({
log.debug("EvaluationSummary setup");
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
const upsertAssignmentCompletionMutation = useMutation(
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
@ -44,7 +44,7 @@ async function submitEvaluation() {
// noinspection TypeScriptValidateTypes
upsertAssignmentCompletionMutation.executeMutation({
assignmentId: props.assignment.id.toString(),
courseSessionId: (courseSessionsStore?.currentCourseSession?.id ?? 0).toString(),
courseSessionId: courseSession.value.id.toString(),
assignmentUserId: props.assignmentUser.user_id.toString(),
completionStatus: "EVALUATION_SUBMITTED",
completionDataString: JSON.stringify({}),
@ -87,7 +87,7 @@ const grade = computed(() => {
const evaluationUser = computed(() => {
if (props.assignmentCompletion.evaluation_user) {
return (courseSessionsStore.currentCourseSession?.users ?? []).find(
return (courseSession.value.users ?? []).find(
(user) => user.user_id === Number(props.assignmentCompletion.evaluation_user)
) as CourseSessionUser;
}

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import ItTextarea from "@/components/ui/ItTextarea.vue";
import { useCurrentCourseSession } from "@/composables";
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import type {
Assignment,
AssignmentCompletion,
@ -24,7 +24,7 @@ const props = defineProps<{
log.debug("EvaluationTask setup", props.taskIndex);
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
const task = computed(() => props.assignment.evaluation_tasks[props.taskIndex]);
@ -68,7 +68,7 @@ async function evaluateAssignmentCompletion(completionData: AssignmentCompletion
// noinspection TypeScriptValidateTypes
upsertAssignmentCompletionMutation.executeMutation({
assignmentId: props.assignment.id.toString(),
courseSessionId: (courseSessionsStore?.currentCourseSession?.id ?? 0).toString(),
courseSessionId: courseSession.value.id.toString(),
assignmentUserId: props.assignmentUser.user_id.toString(),
completionStatus: "EVALUATION_IN_PROGRESS",
completionDataString: JSON.stringify(completionData),

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { useCurrentCourseSession } from "@/composables";
import AssignmentDetails from "@/pages/cockpit/assignmentsPage/AssignmentDetails.vue";
import { calcAssignmentLearningContents } from "@/services/assignmentService";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useLearningPathStore } from "@/stores/learningPath";
import { useUserStore } from "@/stores/user";
import * as log from "loglevel";
@ -14,31 +14,23 @@ const props = defineProps<{
log.debug("AssignmentsPage created", props.courseSlug);
const learningPathStore = useLearningPathStore();
const courseSessionsStore = useCourseSessionsStore();
const userStore = useUserStore();
const courseSession = useCurrentCourseSession();
onMounted(async () => {
log.debug("AssignmentsPage mounted");
});
const assignments = computed(() => {
// TODO: filter by selected circle
if (!courseSessionsStore.currentCourseSession) {
return [];
}
return calcAssignmentLearningContents(
learningPathStore.learningPathForUser(
courseSessionsStore.currentCourseSession.course.slug,
userStore.id
)
learningPathStore.learningPathForUser(courseSession.value.course.slug, userStore.id)
);
});
</script>
<template>
<div class="bg-gray-200">
<div v-if="courseSessionsStore.currentCourseSession" class="container-large">
<div class="container-large">
<nav class="py-4 pb-4">
<router-link
class="btn-text inline-flex items-center pl-0"
@ -58,7 +50,7 @@ const assignments = computed(() => {
<div v-for="assignment in assignments" :key="assignment.id">
<div class="bg-white p-6">
<AssignmentDetails
:course-session="courseSessionsStore.currentCourseSession"
:course-session="courseSession"
:assignment="assignment"
/>
</div>

View File

@ -5,10 +5,10 @@ import ItPersonRow from "@/components/ui/ItPersonRow.vue";
import ItProgress from "@/components/ui/ItProgress.vue";
import type { LearningPath } from "@/services/learningPath";
import { useCurrentCourseSession } from "@/composables";
import AssignmentsTile from "@/pages/cockpit/cockpitPage/AssignmentsTile.vue";
import { useCockpitStore } from "@/stores/cockpit";
import { useCompetenceStore } from "@/stores/competence";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useLearningPathStore } from "@/stores/learningPath";
import { useUserStore } from "@/stores/user";
import groupBy from "lodash/groupBy";
@ -25,7 +25,7 @@ const userStore = useUserStore();
const cockpitStore = useCockpitStore();
const competenceStore = useCompetenceStore();
const learningPathStore = useLearningPathStore();
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
function userCountStatusForCircle(userId: number, translationKey: string) {
const criteria = competenceStore.flatPerformanceCriteria(
@ -102,10 +102,7 @@ function setActiveClasses(translationKey: string) {
</div>
<!-- Status -->
<div class="mb-4 grid grid-rows-2 gap-4 lg:grid-cols-2 lg:grid-rows-none">
<AssignmentsTile
v-if="courseSessionsStore.currentCourseSession"
:course-session="courseSessionsStore.currentCourseSession"
/>
<AssignmentsTile :course-session="courseSession" />
<div class="bg-white px-6 py-5">
<h3 class="heading-3 mb-4 flex items-center gap-2">
<it-icon-test-large class="h-16 w-16"></it-icon-test-large>
@ -124,8 +121,8 @@ function setActiveClasses(translationKey: string) {
learningPathStore.learningPathForUser(props.courseSlug, userStore.id)
?.circles || []
"
:course-id="courseSessionsStore.currentCourseSession?.course.id || 0"
:url="courseSessionsStore.currentCourseSession?.course_url || ''"
:course-id="courseSession.course.id"
:url="courseSession.course_url || ''"
></FeedbackSummary>
<div>
<!-- progress -->

View File

@ -2,6 +2,7 @@
import ItButton from "@/components/ui/ItButton.vue";
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
import { useCurrentCourseSession } from "@/composables";
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
import { useCourseSessionsStore } from "@/stores/courseSessions";
@ -24,6 +25,7 @@ const emit = defineEmits<{
}>();
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
const { t } = useI18n();
const state = reactive({
@ -57,16 +59,10 @@ const onEditTask = (task: AssignmentTask) => {
const onSubmit = async () => {
try {
const courseSessionId = courseSessionsStore.currentCourseSession?.id;
if (!courseSessionId) {
log.error("Invalid courseSessionId");
return;
}
// noinspection TypeScriptValidateTypes
upsertAssignmentCompletionMutation.executeMutation({
assignmentId: props.assignment.id.toString(),
courseSessionId: courseSessionId.toString(),
courseSessionId: courseSession.value.id.toString(),
completionDataString: JSON.stringify({}),
completionStatus: "SUBMITTED",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
import ItTextarea from "@/components/ui/ItTextarea.vue";
import { useCurrentCourseSession } from "@/composables";
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import type {
AssignmentCompletion,
AssignmentCompletionData,
@ -23,7 +23,7 @@ const props = defineProps<{
const checkboxState = reactive({} as Record<string, boolean>);
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
const upsertAssignmentCompletionMutation = useMutation(
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
@ -31,16 +31,10 @@ const upsertAssignmentCompletionMutation = useMutation(
async function upsertAssignmentCompletion(completion_data: AssignmentCompletionData) {
try {
const courseSessionId = courseSessionsStore.currentCourseSession?.id;
if (!courseSessionId) {
console.error("Invalid courseSessionId");
return;
}
// noinspection TypeScriptValidateTypes
await upsertAssignmentCompletionMutation.executeMutation({
assignmentId: props.assignmentId.toString(),
courseSessionId: courseSessionId.toString(),
courseSessionId: courseSession.value.id.toString(),
completionDataString: JSON.stringify(completion_data),
completionStatus: "IN_PROGRESS",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import { useCurrentCourseSession } from "@/composables";
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
import { ASSIGNMENT_COMPLETION_QUERY } from "@/graphql/queries";
import EvaluationSummary from "@/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue";
@ -6,7 +7,6 @@ import AssignmentIntroductionView from "@/pages/learningPath/learningContentPage
import AssignmentSubmissionView from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue";
import AssignmentTaskView from "@/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue";
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useUserStore } from "@/stores/user";
import type {
Assignment,
@ -24,7 +24,7 @@ import { computed, onMounted, reactive } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const courseSessionsStore = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
const userStore = useUserStore();
interface State {
@ -43,7 +43,7 @@ const props = defineProps<{
const queryResult = useQuery({
query: ASSIGNMENT_COMPLETION_QUERY,
variables: {
courseSessionId: courseSessionsStore.currentCourseSession!.id.toString(),
courseSessionId: courseSession.value.id.toString(),
assignmentId: props.learningContent.content_assignment_id.toString(),
},
pause: true,
@ -80,7 +80,7 @@ onMounted(async () => {
// noinspection TypeScriptValidateTypes
await upsertAssignmentCompletionMutation.executeMutation({
assignmentId: props.learningContent.content_assignment_id.toString(),
courseSessionId: courseSessionsStore.currentCourseSession!.id.toString(),
courseSessionId: courseSession.value.id.toString(),
completionDataString: JSON.stringify({}),
completionStatus: "IN_PROGRESS",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@ -109,9 +109,6 @@ const showExitButton = computed(() => numPages.value === stepIndex.value + 1);
const dueDate = computed(() =>
dayjs(state.courseSessionAssignmentDetails?.submissionDeadlineDateTimeUtc)
);
const courseSessionId = computed(
() => courseSessionsStore.currentCourseSession?.id ?? 0
);
const currentTask = computed(() => {
if (stepIndex.value > 0 && stepIndex.value <= numTasks.value) {
return assignment.value?.tasks[stepIndex.value - 1];
@ -154,7 +151,7 @@ const getTitle = () => {
};
const assignmentUser = computed(() => {
return (courseSessionsStore.currentCourseSession?.users ?? []).find(
return courseSession.value.users.find(
(user) => user.user_id === Number(userStore.id)
) as CourseSessionUser;
});
@ -199,7 +196,7 @@ const assignmentUser = computed(() => {
:due-date="dueDate"
:assignment="assignment"
:assignment-completion="assignmentCompletion"
:course-session-id="courseSessionId!"
:course-session-id="courseSession.id"
@edit-task="jumpToTask($event)"
></AssignmentSubmissionView>
</div>

View File

@ -1,6 +1,4 @@
<script setup lang="ts">
import * as log from "loglevel";
import LearningPathAppointmentsMock from "@/pages/learningPath/learningPathPage/LearningPathAppointmentsMock.vue";
import LearningPathListView from "@/pages/learningPath/learningPathPage/LearningPathListView.vue";
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
@ -11,6 +9,7 @@ import LearningPathViewSwitch from "@/pages/learningPath/learningPathPage/Learni
import { useLearningPathStore } from "@/stores/learningPath";
import { useUserStore } from "@/stores/user";
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
import * as log from "loglevel";
import { computed, onMounted, ref } from "vue";
const props = defineProps<{

View File

@ -3,10 +3,10 @@ import { useCircleStore } from "@/stores/circle";
import type { LearningUnit } from "@/types";
import * as log from "loglevel";
import { useCurrentCourseSession } from "@/composables";
import { COMPLETION_FAILURE, COMPLETION_SUCCESS } from "@/constants";
import LearningContentContainer from "@/pages/learningPath/learningContentPage/LearningContentContainer.vue";
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import eventBus from "@/utils/eventBus";
import { useRouteQuery } from "@vueuse/router";
import { computed, onUnmounted } from "vue";
@ -14,7 +14,7 @@ import { computed, onUnmounted } from "vue";
log.debug("LearningContent.vue setup");
const circleStore = useCircleStore();
const courseSession = useCourseSessionsStore();
const courseSession = useCurrentCourseSession();
const questionIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" });
@ -124,7 +124,7 @@ onUnmounted(() => {
<div class="mt-6 lg:mt-12">
{{ $t("selfEvaluation.progressText") }}
<router-link
:to="courseSession.currentCourseSession?.competence_url || '/'"
:to="courseSession.competence_url"
class="text-primary-500 underline"
>
{{ $t("selfEvaluation.progressLink") }}

View File

@ -43,3 +43,16 @@ export const expertRequired: NavigationGuard = (to: RouteLocationNormalized) =>
return `/course/${courseSlug}/learn`;
}
};
export async function handleCourseSessions(to: RouteLocationNormalized) {
// register after login hooks
const courseSessionsStore = useCourseSessionsStore();
if (to.params.courseSlug) {
courseSessionsStore._currentCourseSlug = to.params.courseSlug as string;
} else {
courseSessionsStore._currentCourseSlug = "";
}
if (!courseSessionsStore.loaded) {
await courseSessionsStore.loadCourseSessionsData();
}
}

View File

@ -1,8 +1,10 @@
import DashboardPage from "@/pages/DashboardPage.vue";
import LoginPage from "@/pages/LoginPage.vue";
import { redirectToLoginIfRequired, updateLoggedIn } from "@/router/guards";
import { useAppStore } from "@/stores/app";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import {
handleCourseSessions,
redirectToLoginIfRequired,
updateLoggedIn,
} from "@/router/guards";
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
@ -182,18 +184,7 @@ const router = createRouter({
router.beforeEach(updateLoggedIn);
router.beforeEach(redirectToLoginIfRequired);
router.beforeEach((to) => {
const courseSessionsStore = useCourseSessionsStore();
if (to.params.courseSlug) {
courseSessionsStore._currentCourseSlug = to.params.courseSlug as string;
} else {
courseSessionsStore._currentCourseSlug = "";
}
});
router.afterEach(() => {
const appStore = useAppStore();
appStore.routingFinished = true;
});
// register after login hooks
router.beforeEach(handleCourseSessions);
export default router;

View File

@ -1,17 +0,0 @@
import { defineStore } from "pinia";
export type AppState = {
userLoaded: boolean;
routingFinished: boolean;
};
export const useAppStore = defineStore({
id: "app",
state: () =>
({
userLoaded: false,
routingFinished: false,
} as AppState),
getters: {},
actions: {},
});

View File

@ -1,6 +1,6 @@
import { useCurrentCourseSession } from "@/composables";
import { itGetCached } from "@/fetchHelpers";
import { useCompletionStore } from "@/stores/completion";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useUserStore } from "@/stores/user";
import type {
CircleLight,
@ -166,11 +166,10 @@ export const useCompetenceStore = defineStore({
if (competenceProfilePage) {
const completionStore = useCompletionStore();
const courseSessionsStore = useCourseSessionsStore();
const courseSession = courseSessionsStore.currentCourseSession;
const courseSession = useCurrentCourseSession();
if (courseSession) {
const completionData = await completionStore.loadCourseSessionCompletionData(
courseSession.id,
courseSession.value.id,
userId
);

View File

@ -17,20 +17,16 @@ import { computed, ref } from "vue";
import { useCircleStore } from "./circle";
import { useUserStore } from "./user";
export type LearningSequenceCircleDocument = {
id: number;
title: string;
documents: CircleDocument[];
};
const SELECTED_COURSE_SESSIONS_KEY = "selectedCourseSessionMap";
function loadCourseSessionsData(reload = false) {
log.debug("loadCourseSessionsData called");
const courseSessions = ref<CourseSession[]>([]);
export const useCourseSessionsStore = defineStore("courseSessions", () => {
const loaded = ref(false);
const allCourseSessions = ref<CourseSession[]>([]);
async function loadAndUpdate() {
courseSessions.value = await itGetCached(`/api/course/sessions/`, {
async function loadCourseSessionsData(reload = false) {
log.debug("loadCourseSessionsData called");
allCourseSessions.value = await itGetCached(`/api/course/sessions/`, {
reload: reload,
});
@ -38,7 +34,7 @@ function loadCourseSessionsData(reload = false) {
if (userStore.loggedIn) {
// TODO: refactor after implementing of Klassenkonzept
await Promise.all(
courseSessions.value.map(async (cs) => {
allCourseSessions.value.map(async (cs) => {
const users = (await itGetCached(`/api/course/sessions/${cs.id}/users/`, {
reload: reload,
})) as CourseSessionUser[];
@ -46,26 +42,14 @@ function loadCourseSessionsData(reload = false) {
})
);
if (!courseSessions.value) {
if (!allCourseSessions.value) {
throw `No courseSessionData found for user`;
}
}
loaded.value = true;
}
loadAndUpdate(); // this will be called asynchronously, but does not block
// returns the empty sessions array at first, then after loading populates the ref
return { allCourseSessions: courseSessions };
}
export const useCourseSessionsStore = defineStore("courseSessions", () => {
// using setup function seems cleaner, see https://pinia.vuejs.org/core-concepts/#setup-stores
// this will become a state variable (like each other `ref()`
// store should do own setup, we don't want to have each component initialize it
// that's why we call the load function in here
const { allCourseSessions } = loadCourseSessionsData();
const selectedCourseSessionMap = useLocalStorage(
SELECTED_COURSE_SESSIONS_KEY,
new Map<string, number>()
@ -106,7 +90,7 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
function switchCourseSession(courseSession: CourseSession) {
log.debug("switchCourseSession", courseSession);
selectedCourseSessionMap.value.set(courseSession.course.slug, courseSession.id);
// FIXME: clean up with VBV-305
// Emit event so that the App can re-render with the new courseSession
eventBus.emit("switchedCourseSession", courseSession.id);
}
@ -244,7 +228,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
return {
uniqueCourseSessionsByCourse,
currentCourseSession,
allCurrentCourseSessions,
courseSessionForCourse,
switchCourseSession,
@ -258,6 +241,11 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
findAttendanceDay,
findAssignmentDetails,
// use `useCurrentCourseSession` whenever possible
currentCourseSession,
loadCourseSessionsData,
loaded,
// only used so that `router.afterEach` can switch it
_currentCourseSlug,

View File

@ -2,7 +2,6 @@ import log from "loglevel";
import { bustItGetCache, itGetCached, itPost } from "@/fetchHelpers";
import { loadLocaleMessages, setI18nLanguage } from "@/i18n";
import { useAppStore } from "@/stores/app";
import { defineStore } from "pinia";
const logoutRedirectUrl = import.meta.env.VITE_LOGOUT_REDIRECT || "/";
@ -85,11 +84,9 @@ export const useUserStore = defineStore({
});
},
async fetchUser() {
const appStore = useAppStore();
const data = await itGetCached("/api/core/me/");
this.$state = data;
this.loggedIn = true;
appStore.userLoaded = true;
await setLocale(data.language);
},
async setUserLanguages(language: AvailableLanguages) {

View File

@ -1,8 +1,10 @@
import mitt from "mitt";
export type MittEvents = {
// FIXME: clean up with VBV-305
// event needed so that the App components do re-render
// and reload the current course session
switchedCourseSession: number;
finishedLearningContent: boolean;
};