WIP: Add own components
This commit is contained in:
parent
24aa5d9b8c
commit
6edb5be093
|
|
@ -0,0 +1,36 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, Ref } from "vue";
|
||||
import type { ProgressDashboardCompetenceType } from "@/gql/graphql";
|
||||
import CompetenceSummaryBox from "@/components/dashboard/CompetenceSummaryBox.vue";
|
||||
import { fetchProgressData } from "@/services/dashboard";
|
||||
|
||||
const props = defineProps<{
|
||||
courseId: string;
|
||||
courseSlug: string;
|
||||
sessionToContinueId: string;
|
||||
}>();
|
||||
|
||||
const DEFAULT_COMPETENCE = { total_count: 0, success_count: 0, fail_count: 0 };
|
||||
const competence: Ref<ProgressDashboardCompetenceType> = ref(DEFAULT_COMPETENCE);
|
||||
|
||||
const competenceCriteriaUrl = computed(() => {
|
||||
return `/course/${props.courseSlug}/competence/self-evaluation-and-feedback?courseSessionId=${props.sessionToContinueId}`;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const some = await fetchProgressData(props.courseId);
|
||||
competence.value = some.competence;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="competence" class="mb-14 space-y-8">
|
||||
<div class="flex flex-col space-y-7 bg-white p-6">
|
||||
<CompetenceSummaryBox
|
||||
:fail-count="competence.fail_count"
|
||||
:success-count="competence.success_count"
|
||||
:details-link="competenceCriteriaUrl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import type { CourseProgressType } from "@/gql/graphql";
|
||||
import { DashboardConfigType } from "@/gql/graphql";
|
||||
import { fetchCourseData } from "@/services/dashboard";
|
||||
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
||||
import CompetenceSummary from "@/components/dashboard/CompetenceSummary.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
courseConfig: DashboardConfigType;
|
||||
}>();
|
||||
|
||||
const isLoading = ref(true);
|
||||
|
||||
const data = ref<CourseProgressType | null>(null);
|
||||
|
||||
const courseSlug = computed(() => props.courseConfig?.slug);
|
||||
const courseName = computed(() => props.courseConfig?.name);
|
||||
|
||||
onMounted(async () => {
|
||||
data.value = await fetchCourseData(props.courseConfig.id);
|
||||
isLoading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!isLoading && courseConfig" class="mb-14 space-y-8">
|
||||
<div class="flex flex-col space-y-7 bg-white p-6">
|
||||
<h3>{{ courseName }}</h3>
|
||||
<div v-for="widget in data.widgets" :key="widget.id">
|
||||
{{ widget }}
|
||||
<LearningPathDiagram
|
||||
v-if="
|
||||
widget === 'PROGRESS_WIDGET' && data.session_to_continue_id && courseSlug
|
||||
"
|
||||
:key="courseSlug"
|
||||
:course-slug="courseSlug"
|
||||
:course-session-id="data.session_to_continue_id"
|
||||
diagram-type="horizontal"
|
||||
></LearningPathDiagram>
|
||||
<CompetenceSummary
|
||||
v-else-if="widget === 'COMPETENCE_WIDGET'"
|
||||
:course-slug="courseSlug"
|
||||
:session-to-continue-id="data.session_to_continue_id"
|
||||
:course-id="courseConfig.id"
|
||||
></CompetenceSummary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -23,6 +23,7 @@ const documents = {
|
|||
"\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n action_competences {\n competence_id\n ...CoursePageFields\n performance_criteria {\n competence_id\n learning_unit {\n id\n slug\n evaluate_url\n }\n ...CoursePageFields\n }\n }\n learning_path {\n ...CoursePageFields\n topics {\n is_visible\n ...CoursePageFields\n circles {\n description\n goals\n ...CoursePageFields\n learning_sequences {\n icon\n ...CoursePageFields\n learning_units {\n evaluate_url\n ...CoursePageFields\n performance_criteria {\n ...CoursePageFields\n }\n learning_contents {\n can_user_self_toggle_course_completion\n content_url\n minutes\n description\n ...CoursePageFields\n ... on LearningContentAssignmentObjectType {\n assignment_type\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\n assignment_type\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentRichTextObjectType {\n text\n }\n }\n }\n }\n }\n }\n }\n }\n }\n": types.CourseQueryDocument,
|
||||
"\n query dashboardConfig {\n dashboard_config {\n id\n slug\n name\n dashboard_type\n course_configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n }\n": types.DashboardConfigDocument,
|
||||
"\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n": types.DashboardProgressDocument,
|
||||
"\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n widgets\n }\n }\n": types.DashboardCourseDataDocument,
|
||||
"\n query courseStatistics($courseId: ID!) {\n course_statistics(course_id: $courseId) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n id\n name\n }\n generations\n circles {\n id\n name\n }\n }\n course_session_selection_ids\n course_session_selection_metrics {\n _id\n session_count\n participant_count\n expert_count\n }\n attendance_day_presences {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n due_date\n participants_present\n participants_total\n details_url\n }\n summary {\n _id\n days_completed\n participants_present\n }\n }\n feedback_responses {\n _id\n records {\n _id\n course_session_id\n generation\n circle_id\n experts\n satisfaction_average\n satisfaction_max\n details_url\n }\n summary {\n _id\n satisfaction_average\n satisfaction_max\n total_responses\n }\n }\n assignments {\n _id\n summary {\n _id\n completed_count\n average_passed\n }\n records {\n _id\n course_session_id\n course_session_assignment_id\n circle_id\n generation\n assignment_title\n assignment_type_translation_key\n details_url\n deadline\n metrics {\n _id\n passed_count\n failed_count\n unranked_count\n ranking_completed\n average_passed\n }\n }\n }\n competences {\n _id\n summary {\n _id\n success_total\n fail_total\n }\n records {\n _id\n course_session_id\n generation\n circle_id\n title\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n": types.CourseStatisticsDocument,
|
||||
"\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $learningContentType: String!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n learning_content_type: $learningContentType\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
|
||||
};
|
||||
|
|
@ -81,6 +82,10 @@ export function graphql(source: "\n query dashboardConfig {\n dashboard_conf
|
|||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n"): (typeof documents)["\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n competence {\n _id\n total_count\n success_count\n fail_count\n }\n assignment {\n _id\n total_count\n points_max_count\n points_achieved_count\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n widgets\n }\n }\n"): (typeof documents)["\n query dashboardCourseData($courseId: ID!) {\n course_progress(course_id: $courseId) {\n _id\n course_id\n session_to_continue_id\n widgets\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,7 @@ type Query {
|
|||
course_statistics(course_id: ID!): CourseStatisticsType
|
||||
course_progress(course_id: ID!): CourseProgressType
|
||||
dashboard_config: [DashboardConfigType!]!
|
||||
dashboard_data(course_id: ID!): DashboardConfigType
|
||||
learning_path(id: ID, slug: String, course_id: ID, course_slug: String): LearningPathObjectType
|
||||
course_session_attendance_course(id: ID!, assignment_user_id: ID): CourseSessionAttendanceCourseObjectType
|
||||
course(id: ID, slug: String): CourseObjectType
|
||||
|
|
@ -177,8 +178,9 @@ type CourseProgressType {
|
|||
_id: ID!
|
||||
course_id: ID!
|
||||
session_to_continue_id: ID
|
||||
competence: ProgressDashboardCompetenceType!
|
||||
assignment: ProgressDashboardAssignmentType!
|
||||
competence: ProgressDashboardCompetenceType
|
||||
assignment: ProgressDashboardAssignmentType
|
||||
widgets: [WidgetType!]!
|
||||
}
|
||||
|
||||
type ProgressDashboardCompetenceType {
|
||||
|
|
@ -195,6 +197,14 @@ type ProgressDashboardAssignmentType {
|
|||
points_achieved_count: Int!
|
||||
}
|
||||
|
||||
enum WidgetType {
|
||||
PROGRESS_WIDGET
|
||||
COMPETENCE_WIDGET
|
||||
MENTEE_WIDGET
|
||||
MENTOR_TASKS_WIDGET
|
||||
COMPETENCE_CERTIFICATE_WIDGET
|
||||
}
|
||||
|
||||
type DashboardConfigType {
|
||||
id: ID!
|
||||
name: String!
|
||||
|
|
|
|||
|
|
@ -84,3 +84,4 @@ export const String = "String";
|
|||
export const TopicObjectType = "TopicObjectType";
|
||||
export const UUID = "UUID";
|
||||
export const UserObjectType = "UserObjectType";
|
||||
export const WidgetType = "WidgetType";
|
||||
|
|
|
|||
|
|
@ -331,6 +331,17 @@ export const DASHBOARD_COURSE_SESSION_PROGRESS = graphql(`
|
|||
}
|
||||
`);
|
||||
|
||||
export const DASHBOARD_COURSE_DATA = graphql(`
|
||||
query dashboardCourseData($courseId: ID!) {
|
||||
course_progress(course_id: $courseId) {
|
||||
_id
|
||||
course_id
|
||||
session_to_continue_id
|
||||
widgets
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const DASHBOARD_COURSE_STATISTICS = graphql(`
|
||||
query courseStatistics($courseId: ID!) {
|
||||
course_statistics(course_id: $courseId) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
|
|||
import CourseDetailDates from "@/components/dashboard/CourseDetailDates.vue";
|
||||
import NoCourseSession from "@/components/dashboard/NoCourseSession.vue";
|
||||
import MentorPage from "@/pages/dashboard/MentorPage.vue";
|
||||
import CoursePanel from "@/components/dashboard/CoursePanel.vue";
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
|
|
@ -50,6 +51,12 @@ onMounted(dashboardStore.loadDashboardDetails);
|
|||
></ItDropdownSelect>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li v-for="config in dashboardStore.dashboardConfigs" :key="config.id">
|
||||
<p>{{ config }}</p>
|
||||
<CoursePanel :course-config="config" />
|
||||
</li>
|
||||
</ul>
|
||||
<component
|
||||
:is="boards[dashboardStore.currentDashboardConfig.dashboard_type].main"
|
||||
></component>
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export async function handleCurrentCourseSession(to: RouteLocationNormalized) {
|
|||
courseSessionsStore._currentCourseSlug = "";
|
||||
}
|
||||
if (!courseSessionsStore.loaded) {
|
||||
console.log("handleCurrentCourseSession: loadCourseSessionsData");
|
||||
await courseSessionsStore.loadCourseSessionsData();
|
||||
}
|
||||
}
|
||||
|
|
@ -77,6 +78,7 @@ export async function handleCourseSessionAsQueryParam(to: RouteLocationNormalize
|
|||
if (userStore.loggedIn) {
|
||||
const courseSessionsStore = useCourseSessionsStore();
|
||||
if (!courseSessionsStore.loaded) {
|
||||
console.log("handleCourseSessionAsQueryParam: loadCourseSessionsData");
|
||||
await courseSessionsStore.loadCourseSessionsData();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -387,8 +387,25 @@ router.beforeEach(updateLoggedIn);
|
|||
router.beforeEach(redirectToLoginIfRequired);
|
||||
|
||||
// register after login hooks
|
||||
router.beforeEach(handleCurrentCourseSession);
|
||||
router.beforeEach(handleCourseSessionAsQueryParam);
|
||||
router.beforeEach(
|
||||
async (to, from) =>
|
||||
await ignoreGuardsForHomeRoute(to, from, handleCurrentCourseSession)
|
||||
);
|
||||
|
||||
router.beforeEach(
|
||||
async (to, from) =>
|
||||
await ignoreGuardsForHomeRoute(to, from, handleCourseSessionAsQueryParam)
|
||||
);
|
||||
|
||||
async function ignoreGuardsForHomeRoute(
|
||||
to: RouteLocationNormalized,
|
||||
from: RouteLocationNormalized,
|
||||
guard: NavigationGuardNext
|
||||
) {
|
||||
if (to.name !== "home") {
|
||||
return await guard(to, from);
|
||||
}
|
||||
}
|
||||
|
||||
router.beforeEach(addToHistory);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { graphqlClient } from "@/graphql/client";
|
||||
import {
|
||||
DASHBOARD_CONFIG,
|
||||
DASHBOARD_COURSE_DATA,
|
||||
DASHBOARD_COURSE_SESSION_PROGRESS,
|
||||
DASHBOARD_COURSE_STATISTICS,
|
||||
} from "@/graphql/queries";
|
||||
|
|
@ -61,3 +62,21 @@ export const fetchDashboardConfig = async (): Promise<DashboardConfigType[] | nu
|
|||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchCourseData = async (
|
||||
courseId: string
|
||||
): Promise<CourseProgressType | null> => {
|
||||
try {
|
||||
const res = await graphqlClient.query(DASHBOARD_COURSE_DATA, {
|
||||
courseId,
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
console.error("Error fetching data for course ID:", courseId, res.error);
|
||||
}
|
||||
return res.data?.course_progress || null;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data for course ID: ${courseId}`, error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from vbv_lernwelt.dashboard.graphql.types.dashboard import (
|
|||
DashboardType,
|
||||
ProgressDashboardAssignmentType,
|
||||
ProgressDashboardCompetenceType,
|
||||
WidgetType,
|
||||
)
|
||||
from vbv_lernwelt.iam.permissions import (
|
||||
can_view_course_session,
|
||||
|
|
@ -39,6 +40,10 @@ class DashboardQuery(graphene.ObjectType):
|
|||
graphene.NonNull(DashboardConfigType), required=True
|
||||
)
|
||||
|
||||
dashboard_data = graphene.Field(
|
||||
DashboardConfigType, course_id=graphene.ID(required=True)
|
||||
)
|
||||
|
||||
def resolve_course_statistics(root, info, course_id: str): # noqa
|
||||
user = info.context.user
|
||||
course = Course.objects.get(id=course_id)
|
||||
|
|
@ -164,8 +169,12 @@ class DashboardQuery(graphene.ObjectType):
|
|||
points_max_count=int(points_max_count), # noqa
|
||||
points_achieved_count=int(points_achieved_count), # noqa
|
||||
),
|
||||
widgets=get_widget_for_course(course_id, user), # noqa
|
||||
)
|
||||
|
||||
def resolve_dashboard_data(root, info, course_id: str):
|
||||
return get_widget_for_course(course_id, info.context.user)
|
||||
|
||||
|
||||
def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Set[int]]:
|
||||
course_ids = set()
|
||||
|
|
@ -222,7 +231,6 @@ def get_user_course_session_dashboards(
|
|||
sessions of a course, but with varying permissions?
|
||||
-> We just show the simple list dashboard for now.
|
||||
"""
|
||||
|
||||
dashboards = []
|
||||
course_ids = set()
|
||||
|
||||
|
|
@ -246,6 +254,7 @@ def get_user_course_session_dashboards(
|
|||
resolved_dashboard_type = DashboardType.SIMPLE_DASHBOARD
|
||||
elif course_role == CourseSessionUser.Role.MEMBER:
|
||||
resolved_dashboard_type = DashboardType.PROGRESS_DASHBOARD
|
||||
|
||||
else:
|
||||
# fallback: just go with simple list dashboard
|
||||
resolved_dashboard_type = DashboardType.SIMPLE_DASHBOARD
|
||||
|
|
@ -263,3 +272,42 @@ def get_user_course_session_dashboards(
|
|||
)
|
||||
|
||||
return dashboards, course_ids
|
||||
|
||||
|
||||
def get_widget_for_course(course_id: str, user: User) -> List[WidgetType]:
|
||||
widgets = []
|
||||
|
||||
course_sessions = CourseSession.objects.filter(course__id=course_id).prefetch_related(
|
||||
"course",
|
||||
"course__configuration",
|
||||
)
|
||||
roles_by_course: Dict[Course, Set[DashboardType]] = {}
|
||||
|
||||
learning_mentors = LearningMentor.objects.filter(mentor=user).values_list(
|
||||
"course_session__course__id", "id"
|
||||
)
|
||||
mentor_course_ids = set([mentor[0] for mentor in learning_mentors])
|
||||
|
||||
# duplicate code
|
||||
for course_session in course_sessions:
|
||||
if can_view_course_session(user=user, course_session=course_session):
|
||||
role = CourseSessionUser.objects.get(
|
||||
course_session=course_session, user=user
|
||||
).role
|
||||
roles_by_course.setdefault(course_session.course, set())
|
||||
roles_by_course[course_session.course].add(role)
|
||||
|
||||
for course, roles in roles_by_course.items():
|
||||
if len(roles) == 1:
|
||||
course_role = roles.pop()
|
||||
|
||||
# test widgets
|
||||
if course_role == CourseSessionUser.Role.MEMBER:
|
||||
widgets.append(WidgetType.PROGRESS_WIDGET)
|
||||
widgets.append(WidgetType.COMPETENCE_WIDGET)
|
||||
if course.configuration.enable_competence_certificates:
|
||||
widgets.append(WidgetType.COMPETENCE_CERTIFICATE_WIDGET)
|
||||
if course.id in mentor_course_ids:
|
||||
widgets.append(WidgetType.MENTOR_TASKS_WIDGET)
|
||||
# add KN if has KN
|
||||
return widgets
|
||||
|
|
|
|||
|
|
@ -56,6 +56,14 @@ class DashboardType(Enum):
|
|||
MENTOR_DASHBOARD = "MentorDashboard"
|
||||
|
||||
|
||||
class WidgetType(Enum):
|
||||
PROGRESS_WIDGET = "ProgressWidget"
|
||||
COMPETENCE_WIDGET = "CompetenceWidget"
|
||||
MENTEE_WIDGET = "MenteeWidget"
|
||||
MENTOR_TASKS_WIDGET = "MentorTasksWidget"
|
||||
COMPETENCE_CERTIFICATE_WIDGET = "CompetenceCertificateWidget"
|
||||
|
||||
|
||||
class DashboardConfigType(graphene.ObjectType):
|
||||
id = graphene.ID(required=True)
|
||||
name = graphene.String(required=True)
|
||||
|
|
@ -64,6 +72,11 @@ class DashboardConfigType(graphene.ObjectType):
|
|||
course_configuration = graphene.Field(CourseConfigurationObjectType, required=True)
|
||||
|
||||
|
||||
class DashboardDataType(graphene.ObjectType):
|
||||
id = graphene.ID(required=True)
|
||||
widgets = graphene.List(graphene.NonNull(WidgetType), required=True)
|
||||
|
||||
|
||||
class ProgressDashboardCompetenceType(graphene.ObjectType):
|
||||
_id = graphene.ID(required=True)
|
||||
total_count = graphene.Int(required=True)
|
||||
|
|
@ -82,8 +95,9 @@ class CourseProgressType(graphene.ObjectType):
|
|||
_id = graphene.ID(required=True)
|
||||
course_id = graphene.ID(required=True)
|
||||
session_to_continue_id = graphene.ID(required=False)
|
||||
competence = graphene.Field(ProgressDashboardCompetenceType, required=True)
|
||||
assignment = graphene.Field(ProgressDashboardAssignmentType, required=True)
|
||||
competence = graphene.Field(ProgressDashboardCompetenceType, required=False)
|
||||
assignment = graphene.Field(ProgressDashboardAssignmentType, required=False)
|
||||
widgets = graphene.List(graphene.NonNull(WidgetType), required=True)
|
||||
|
||||
|
||||
class CourseStatisticsType(graphene.ObjectType):
|
||||
|
|
|
|||
Loading…
Reference in New Issue