feat: stats dashboard

This commit is contained in:
Reto Aebersold 2023-10-26 17:25:53 +02:00
parent ae4f4d2611
commit d66e392c73
11 changed files with 492 additions and 209 deletions

View File

@ -22,7 +22,8 @@ const documents = {
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument,
"\n query courseQuery($slug: String!) {\n course(slug: $slug) {\n id\n title\n slug\n category_name\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 }\n competence_certificate {\n ...CoursePageFields\n }\n }\n ... on LearningContentEdoniqTestObjectType {\n checkbox_text\n has_extended_time_test\n content_assignment {\n id\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 }\n }\n": types.DashboardConfigDocument,
"\n query dashboardProgress($courseId: ID!) {\n course_progress(course_id: $courseId) {\n 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 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 courseStatistics($course_id: ID!) {\n course_statistics(course_id: $course_id) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n _id\n session_id\n session_title\n }\n generations\n circles {\n _id\n circle_id\n circle_title\n experts\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 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 performances {\n _id\n course_session_id\n generation\n circle_id\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 $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\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,
};
@ -79,7 +80,11 @@ 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 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 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"];
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 courseStatistics($course_id: ID!) {\n course_statistics(course_id: $course_id) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n _id\n session_id\n session_title\n }\n generations\n circles {\n _id\n circle_id\n circle_title\n experts\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 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 performances {\n _id\n course_session_id\n generation\n circle_id\n success_count\n fail_count\n details_url\n }\n }\n }\n }\n"): (typeof documents)["\n query courseStatistics($course_id: ID!) {\n course_statistics(course_id: $course_id) {\n _id\n course_id\n course_title\n course_slug\n course_session_properties {\n _id\n sessions {\n _id\n session_id\n session_title\n }\n generations\n circles {\n _id\n circle_id\n circle_title\n experts\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 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 performances {\n _id\n course_session_id\n generation\n circle_id\n success_count\n fail_count\n details_url\n }\n }\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

View File

@ -23,47 +23,54 @@ type Query {
}
type CourseStatisticsType {
id: ID!
_id: ID!
course_id: ID!
course_title: String!
course_slug: String!
course_session_properties: CourseSessionProperties!
course_session_properties: StatisticsCourseSessionPropertiesType!
course_session_selection_ids: [ID]!
course_session_selection_metrics: CourseSessionsSelectionMetrics!
attendance_day_presences: AttendanceDayPresences!
feedback_responses: FeedbackResponses!
assignments: Assignments!
competences: Competences!
course_session_selection_metrics: StatisticsCourseSessionsSelectionMetricType!
attendance_day_presences: AttendanceDayPresencesStatisticsType!
feedback_responses: FeedbackStatisticsResponsesType!
assignments: AssignmentsStatisticsType!
competences: CompetencesStatisticsType!
}
type CourseSessionProperties {
sessions: [CourseSessionData]!
type StatisticsCourseSessionPropertiesType {
_id: ID!
sessions: [StatisticsCourseSessionDataType]!
generations: [String]!
circles: [CircleData]!
circles: [StatisticsCircleDataType]!
}
type CourseSessionData {
type StatisticsCourseSessionDataType {
_id: ID!
session_id: ID!
session_title: String!
}
type CircleData {
type StatisticsCircleDataType {
_id: ID!
circle_id: ID!
circle_title: String!
experts: [String]!
}
type CourseSessionsSelectionMetrics {
type StatisticsCourseSessionsSelectionMetricType {
_id: ID!
session_count: Int!
participant_count: Int!
expert_count: Int!
}
type AttendanceDayPresences {
records: [PresenceRecord]!
summary: AttendanceSummary!
type AttendanceDayPresencesStatisticsType {
_id: ID!
records: [PresenceRecordStatisticsType]!
summary: AttendanceSummaryStatisticsType!
}
type PresenceRecord {
type PresenceRecordStatisticsType {
_id: ID!
course_session_id: ID!
generation: String!
circle_id: ID!
@ -73,17 +80,20 @@ type PresenceRecord {
details_url: String!
}
type AttendanceSummary {
type AttendanceSummaryStatisticsType {
_id: ID!
days_completed: Int!
participants_present: Int!
}
type FeedbackResponses {
records: [FeedbackRecord]!
summary: FeedbackSummary!
type FeedbackStatisticsResponsesType {
_id: ID!
records: [FeedbackStatisticsRecordType]!
summary: FeedbackStatisticsSummaryType!
}
type FeedbackRecord {
type FeedbackStatisticsRecordType {
_id: ID!
course_session_id: ID!
generation: String!
circle_id: ID!
@ -92,18 +102,21 @@ type FeedbackRecord {
details_url: String!
}
type FeedbackSummary {
type FeedbackStatisticsSummaryType {
_id: ID!
satisfaction_average: Float!
satisfaction_max: Int!
total_responses: Int!
}
type Assignments {
records: [AssignmentRecord]!
summary: AssignmentSummary!
type AssignmentsStatisticsType {
_id: ID!
records: [AssignmentStatisticsRecordType]!
summary: AssignmentStatisticsSummaryType!
}
type AssignmentRecord {
type AssignmentStatisticsRecordType {
_id: ID!
course_session_id: ID!
course_session_assignment_id: ID!
circle_id: ID!
@ -111,7 +124,7 @@ type AssignmentRecord {
assignment_type_translation_key: String!
assignment_title: String!
deadline: DateTime!
metrics: AssignmentCompletionMetrics!
metrics: AssignmentCompletionMetricsType!
details_url: String!
}
@ -122,7 +135,8 @@ value as specified by
"""
scalar DateTime
type AssignmentCompletionMetrics {
type AssignmentCompletionMetricsType {
_id: ID!
passed_count: Int!
failed_count: Int!
unranked_count: Int!
@ -130,17 +144,26 @@ type AssignmentCompletionMetrics {
average_passed: Float!
}
type AssignmentSummary {
type AssignmentStatisticsSummaryType {
_id: ID!
completed_count: Int!
average_passed: Float!
}
type Competences {
performances: [CompetencePerformance]!
summary: CompletionSummary!
type CompetencesStatisticsType {
_id: ID!
summary: CompetencePerformanceStatisticsSummaryType!
performances: [CompetencePerformanceStatisticsType]!
}
type CompetencePerformance {
type CompetencePerformanceStatisticsSummaryType {
_id: ID!
success_total: Int!
fail_total: Int!
}
type CompetencePerformanceStatisticsType {
_id: ID!
course_session_id: ID!
generation: String!
circle_id: ID!
@ -149,13 +172,9 @@ type CompetencePerformance {
details_url: String!
}
type CompletionSummary {
success_total: Int!
fail_total: Int!
}
type CourseProgressType {
id: ID!
_id: ID!
course_id: ID!
session_to_continue_id: ID
competence: ProgressDashboardCompetenceType!
assignment: ProgressDashboardAssignmentType!

View File

@ -1,29 +1,28 @@
export const ActionCompetenceObjectType = "ActionCompetenceObjectType";
export const AssignmentAssignmentAssignmentTypeChoices = "AssignmentAssignmentAssignmentTypeChoices";
export const AssignmentAssignmentCompletionCompletionStatusChoices = "AssignmentAssignmentCompletionCompletionStatusChoices";
export const AssignmentCompletionMetrics = "AssignmentCompletionMetrics";
export const AssignmentCompletionMetricsType = "AssignmentCompletionMetricsType";
export const AssignmentCompletionMutation = "AssignmentCompletionMutation";
export const AssignmentCompletionObjectType = "AssignmentCompletionObjectType";
export const AssignmentCompletionStatus = "AssignmentCompletionStatus";
export const AssignmentObjectType = "AssignmentObjectType";
export const AssignmentRecord = "AssignmentRecord";
export const AssignmentSummary = "AssignmentSummary";
export const Assignments = "Assignments";
export const AssignmentStatisticsRecordType = "AssignmentStatisticsRecordType";
export const AssignmentStatisticsSummaryType = "AssignmentStatisticsSummaryType";
export const AssignmentsStatisticsType = "AssignmentsStatisticsType";
export const AttendanceCourseUserMutation = "AttendanceCourseUserMutation";
export const AttendanceDayPresences = "AttendanceDayPresences";
export const AttendanceSummary = "AttendanceSummary";
export const AttendanceDayPresencesStatisticsType = "AttendanceDayPresencesStatisticsType";
export const AttendanceSummaryStatisticsType = "AttendanceSummaryStatisticsType";
export const AttendanceUserInputType = "AttendanceUserInputType";
export const AttendanceUserObjectType = "AttendanceUserObjectType";
export const AttendanceUserStatus = "AttendanceUserStatus";
export const Boolean = "Boolean";
export const CircleData = "CircleData";
export const CircleLightObjectType = "CircleLightObjectType";
export const CircleObjectType = "CircleObjectType";
export const CompetenceCertificateListObjectType = "CompetenceCertificateListObjectType";
export const CompetenceCertificateObjectType = "CompetenceCertificateObjectType";
export const CompetencePerformance = "CompetencePerformance";
export const Competences = "Competences";
export const CompletionSummary = "CompletionSummary";
export const CompetencePerformanceStatisticsSummaryType = "CompetencePerformanceStatisticsSummaryType";
export const CompetencePerformanceStatisticsType = "CompetencePerformanceStatisticsType";
export const CompetencesStatisticsType = "CompetencesStatisticsType";
export const CoreUserLanguageChoices = "CoreUserLanguageChoices";
export const CourseCourseSessionUserRoleChoices = "CourseCourseSessionUserRoleChoices";
export const CourseObjectType = "CourseObjectType";
@ -31,13 +30,10 @@ export const CoursePageInterface = "CoursePageInterface";
export const CourseProgressType = "CourseProgressType";
export const CourseSessionAssignmentObjectType = "CourseSessionAssignmentObjectType";
export const CourseSessionAttendanceCourseObjectType = "CourseSessionAttendanceCourseObjectType";
export const CourseSessionData = "CourseSessionData";
export const CourseSessionEdoniqTestObjectType = "CourseSessionEdoniqTestObjectType";
export const CourseSessionObjectType = "CourseSessionObjectType";
export const CourseSessionProperties = "CourseSessionProperties";
export const CourseSessionUserExpertCircleType = "CourseSessionUserExpertCircleType";
export const CourseSessionUserObjectsType = "CourseSessionUserObjectsType";
export const CourseSessionsSelectionMetrics = "CourseSessionsSelectionMetrics";
export const CourseStatisticsType = "CourseStatisticsType";
export const DashboardConfigType = "DashboardConfigType";
export const DashboardType = "DashboardType";
@ -45,10 +41,10 @@ export const Date = "Date";
export const DateTime = "DateTime";
export const DueDateObjectType = "DueDateObjectType";
export const ErrorType = "ErrorType";
export const FeedbackRecord = "FeedbackRecord";
export const FeedbackResponseObjectType = "FeedbackResponseObjectType";
export const FeedbackResponses = "FeedbackResponses";
export const FeedbackSummary = "FeedbackSummary";
export const FeedbackStatisticsRecordType = "FeedbackStatisticsRecordType";
export const FeedbackStatisticsResponsesType = "FeedbackStatisticsResponsesType";
export const FeedbackStatisticsSummaryType = "FeedbackStatisticsSummaryType";
export const Float = "Float";
export const GenericScalar = "GenericScalar";
export const ID = "ID";
@ -72,11 +68,15 @@ export const LearningUnitObjectType = "LearningUnitObjectType";
export const LearnpathLearningContentAssignmentAssignmentTypeChoices = "LearnpathLearningContentAssignmentAssignmentTypeChoices";
export const Mutation = "Mutation";
export const PerformanceCriteriaObjectType = "PerformanceCriteriaObjectType";
export const PresenceRecord = "PresenceRecord";
export const PresenceRecordStatisticsType = "PresenceRecordStatisticsType";
export const ProgressDashboardAssignmentType = "ProgressDashboardAssignmentType";
export const ProgressDashboardCompetenceType = "ProgressDashboardCompetenceType";
export const Query = "Query";
export const SendFeedbackMutation = "SendFeedbackMutation";
export const StatisticsCircleDataType = "StatisticsCircleDataType";
export const StatisticsCourseSessionDataType = "StatisticsCourseSessionDataType";
export const StatisticsCourseSessionPropertiesType = "StatisticsCourseSessionPropertiesType";
export const StatisticsCourseSessionsSelectionMetricType = "StatisticsCourseSessionsSelectionMetricType";
export const String = "String";
export const TopicObjectType = "TopicObjectType";
export const UUID = "UUID";

View File

@ -283,7 +283,8 @@ export const DASHBOARD_CONFIG = graphql(`
export const DASHBOARD_COURSE_SESSION_PROGRESS = graphql(`
query dashboardProgress($courseId: ID!) {
course_progress(course_id: $courseId) {
id
_id
course_id
session_to_continue_id
competence {
_id
@ -300,3 +301,116 @@ export const DASHBOARD_COURSE_SESSION_PROGRESS = graphql(`
}
}
`);
export const DASHBOARD_COURSE_STATISTICS = graphql(`
query courseStatistics($course_id: ID!) {
course_statistics(course_id: $course_id) {
_id
course_id
course_title
course_slug
course_session_properties {
_id
sessions {
_id
session_id
session_title
}
generations
circles {
_id
circle_id
circle_title
experts
}
}
course_session_selection_ids
course_session_selection_metrics {
_id
session_count
participant_count
expert_count
}
attendance_day_presences {
_id
records {
_id
course_session_id
generation
circle_id
due_date
participants_present
participants_total
details_url
}
summary {
_id
days_completed
participants_present
}
}
feedback_responses {
_id
records {
_id
course_session_id
generation
circle_id
satisfaction_average
satisfaction_max
details_url
}
summary {
_id
satisfaction_average
satisfaction_max
total_responses
}
}
assignments {
_id
summary {
_id
completed_count
average_passed
}
records {
_id
course_session_id
course_session_assignment_id
circle_id
generation
assignment_title
assignment_type_translation_key
details_url
deadline
metrics {
_id
passed_count
failed_count
unranked_count
ranking_completed
average_passed
}
}
}
competences {
_id
summary {
_id
success_total
fail_total
}
performances {
_id
course_session_id
generation
circle_id
success_count
fail_count
details_url
}
}
}
}
`);

View File

@ -47,66 +47,89 @@ const assignment_progress = computed(() => ({
FAIL: 0,
UNKNOWN: assignment.value.points_max_count - assignment.value.points_achieved_count,
}));
const competenceCertificateUrl = computed(() => {
return `/course/${courseSlug.value}/competence/certificates?courseSessionId=${progress.value?.course_progress?.session_to_continue_id}`;
});
const competenceCriteriaUrl = computed(() => {
return `/course/${courseSlug.value}/competence/criteria?courseSessionId=${progress.value?.course_progress?.session_to_continue_id}`;
});
</script>
<template>
<div v-if="progress?.course_progress && dashboardStore.currentDashboardConfig">
<div class="mb-14 space-y-8">
<div class="flex flex-col space-y-7 bg-white p-6">
<h3>{{ courseName }}</h3>
<LearningPathDiagram
v-if="progress.course_progress.session_to_continue_id && courseSlug"
:key="courseSlug"
:course-slug="courseSlug"
:course-session-id="progress.course_progress.session_to_continue_id"
diagram-type="horizontal"
></LearningPathDiagram>
<div>
<router-link
class="btn-blue"
:to="getLearningPathUrl(dashboardStore.currentDashboardConfig.slug)"
:data-cy="`continue-course-${dashboardStore.currentDashboardConfig.id}`"
>
{{ $t("general.nextStep") }}
</router-link>
</div>
<div
v-if="progress?.course_progress && dashboardStore.currentDashboardConfig"
class="mb-14 space-y-8"
>
<div class="flex flex-col space-y-7 bg-white p-6">
<h3>{{ courseName }}</h3>
<LearningPathDiagram
v-if="progress.course_progress.session_to_continue_id && courseSlug"
:key="courseSlug"
:course-slug="courseSlug"
:course-session-id="progress.course_progress.session_to_continue_id"
diagram-type="horizontal"
></LearningPathDiagram>
<div>
<router-link
class="btn-blue"
:to="getLearningPathUrl(dashboardStore.currentDashboardConfig.slug)"
:data-cy="`continue-course-${dashboardStore.currentDashboardConfig.id}`"
>
{{ $t("general.nextStep") }}
</router-link>
</div>
<div class="grid auto-rows-fr grid-cols-1 gap-8 xl:grid-cols-2">
<div class="flex flex-col space-y-4 bg-white p-6">
<h4 class="mb-1 font-bold">Kompetenznachweis-Elemente</h4>
<div class="flex items-center space-x-3">
<span class="text-4xl font-bold">
{{ assignment.total_count }}
</span>
<span>Elemente abgeschlossen</span>
</div>
<div class="flex items-center space-x-3">
<span class="text-4xl font-bold">
{{ assignment.points_achieved_count }} von
{{ assignment.points_max_count }}
</span>
<span>Punkten erreicht</span>
</div>
<ItProgress :status-count="assignment_progress"></ItProgress>
<router-link class="pt-8 underline" to="#">Details anschauen</router-link>
</div>
<div class="grid auto-rows-fr grid-cols-1 gap-8 xl:grid-cols-2">
<div class="flex flex-col space-y-4 bg-white p-6">
<h4 class="mb-1 font-bold">{{ $t("a.Kompetenznachweis-Elemente") }}</h4>
<div class="flex items-center">
<i18next :translation="$t('a.NUMBER Elemente abgeschlossen')">
<template #NUMBER>
<span class="mr-3 text-4xl font-bold">
{{ assignment.total_count }}
</span>
</template>
</i18next>
</div>
<div class="flex flex-col space-y-4 bg-white p-6">
<h4 class="mb-1 font-bold">Selbsteinschätzungen</h4>
<div class="flex items-center space-x-3">
<it-icon-smiley-happy class="h-12 w-12"></it-icon-smiley-happy>
<span class="text-4xl font-bold">{{ competence.success_count }}</span>
<span>Ich kann das</span>
</div>
<div class="flex items-center space-x-3">
<it-icon-smiley-thinking class="h-12 w-12"></it-icon-smiley-thinking>
<span class="text-4xl font-bold">{{ competence.fail_count }}</span>
<span>Das muss ich nochmals anschauen</span>
</div>
<router-link class="pt-8 underline" to="#">Details anschauen</router-link>
<div class="flex items-center">
<i18next :translation="$t('a.xOfY Punkten erreicht')">
<template #xOfY>
<span class="mr-3 text-4xl font-bold">
{{
$t("a.VALUE von MAXIMUM", {
VALUE: assignment.points_achieved_count,
MAXIMUM: assignment.points_max_count,
})
}}
</span>
</template>
</i18next>
</div>
<ItProgress :status-count="assignment_progress"></ItProgress>
<router-link class="pt-8 underline" :to="competenceCertificateUrl">
{{ $t("a.Details anschauen") }}
</router-link>
</div>
<div class="flex flex-col space-y-4 bg-white p-6">
<h4 class="mb-1 font-bold">{{ $t("a.Selbsteinschätzungen") }}</h4>
<div class="flex items-center space-x-3">
<it-icon-smiley-happy class="h-12 w-12"></it-icon-smiley-happy>
<span class="text-4xl font-bold">{{ competence.success_count }}</span>
<span>{{ $t("a.Ich kann das") }}</span>
</div>
<div class="flex items-center space-x-3">
<it-icon-smiley-thinking class="h-12 w-12"></it-icon-smiley-thinking>
<span class="text-4xl font-bold">{{ competence.fail_count }}</span>
<span>{{ $t("a.Das muss ich nochmals anschauen") }}</span>
</div>
<router-link class="pt-8 underline" :to="competenceCriteriaUrl">
{{ $t("a.Details anschauen") }}
</router-link>
</div>
</div>
</div>

View File

@ -1,7 +1,63 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { useDashboardStore } from "@/stores/dashboard";
import { onMounted } from "vue";
import ItProgress from "@/components/ui/ItProgress.vue";
const dashboardStore = useDashboardStore();
onMounted(dashboardStore.loadDashboardStatistics);
</script>
<template>
<h4>Stats</h4>
<div v-if="dashboardStore.currentCourseStatistics" class="mb-14 space-y-8">
<div class="flex items-center justify-between bg-white p-6">
<div class="flex space-x-6">
<div class="flex items-center space-x-2">
<span class="text-4xl font-bold">352</span>
<span>Teilnehmer</span>
</div>
<div class="flex items-center space-x-2">
<span class="text-4xl font-bold">25</span>
<span>Trainer</span>
</div>
<div class="flex items-center space-x-2">
<span class="text-4xl font-bold">18</span>
<span>Durchführungen</span>
</div>
</div>
<router-link class="btn-secondary" to="#">
{{ $t("a.Liste anzeigen") }}
</router-link>
</div>
<div class="grid auto-rows-fr grid-cols-1 gap-8 xl:grid-cols-2">
<div class="flex flex-col space-y-4 bg-white p-6">
<h4 class="mb-1 font-bold">{{ $t("a.Anwesenheit") }}</h4>
<div class="flex items-center">
<i18next :translation="$t('a.NUMBER Präsenztage abgeschlossen')">
<template #NUMBER>
<span class="mr-3 text-4xl font-bold">
{{
dashboardStore.currentCourseStatistics.attendance_day_presences
.summary.days_completed
}}
</span>
</template>
</i18next>
</div>
<div class="flex items-center">
<i18next :translation="$t('a.NUMBER Präsenztage abgeschlossen')">
<template #NUMBER>
<span class="mr-3 text-4xl font-bold">
{{
dashboardStore.currentCourseStatistics.attendance_day_presences
.summary.days_completed
}}
</span>
</template>
</i18next>
</div>
<ItProgress></ItProgress>
</div>
</div>
</div>
</template>
<style scoped></style>

View File

@ -1,6 +1,6 @@
import type { DashboardConfigType } from "@/gql/graphql";
import type { CourseStatisticsType, DashboardConfigType } from "@/gql/graphql";
import { graphqlClient } from "@/graphql/client";
import { DASHBOARD_CONFIG } from "@/graphql/queries";
import { DASHBOARD_CONFIG, DASHBOARD_COURSE_STATISTICS } from "@/graphql/queries";
import { defineStore } from "pinia";
import type { Ref } from "vue";
import { ref } from "vue";
@ -8,9 +8,14 @@ import { ref } from "vue";
export const useDashboardStore = defineStore("dashboard", () => {
const dashboardConfigs: Ref<DashboardConfigType[]> = ref([]);
const currentDashboardConfig: Ref<DashboardConfigType | undefined> = ref();
const courseStatisticsCache: Record<string, CourseStatisticsType | null> = {};
const currentCourseStatistics: Ref<CourseStatisticsType | null> = ref(null);
const setCurrentDashboardConfig = (config: DashboardConfigType) => {
const setCurrentDashboardConfig = async (config: DashboardConfigType) => {
currentDashboardConfig.value = config;
if (config.dashboard_type === "STATISTICS_DASHBOARD") {
await loadDashboardStatistics();
}
};
const loadDashboardConfig = async () => {
@ -18,6 +23,35 @@ export const useDashboardStore = defineStore("dashboard", () => {
if (res.data) {
dashboardConfigs.value = res.data.dashboard_config;
currentDashboardConfig.value = res.data.dashboard_config[0];
} else {
console.warn("No dashboard config found");
}
};
const loadDashboardStatistics = async () => {
if (!currentDashboardConfig.value) {
currentCourseStatistics.value = null;
return;
}
const courseId = currentDashboardConfig.value.id;
if (courseStatisticsCache[courseId]) {
currentCourseStatistics.value = courseStatisticsCache[courseId];
return;
}
const res = await graphqlClient.query(DASHBOARD_COURSE_STATISTICS, {
course_id: courseId,
});
if (res.data && res.data.course_statistics) {
courseStatisticsCache[courseId] = res.data.course_statistics;
currentCourseStatistics.value = res.data.course_statistics;
} else {
console.warn(`No course statistics found for course ID: ${courseId}`);
courseStatisticsCache[courseId] = null;
currentCourseStatistics.value = null;
}
};
@ -26,5 +60,7 @@ export const useDashboardStore = defineStore("dashboard", () => {
currentDashboardConfig,
setCurrentDashboardConfig,
loadDashboardConfig,
loadDashboardStatistics,
currentCourseStatistics,
};
});

View File

@ -68,7 +68,7 @@ class DashboardQuery(graphene.ObjectType):
courses = Course.objects.all().values("id", "title", "slug")
return [
{
"_id": c["id"],
"id": c["id"],
"course_id": c["id"],
"name": c["title"],
"slug": c["slug"],

View File

@ -58,9 +58,12 @@ def feedback_responses(
)
)
avg = sum([fb.satisfaction_average for fb in circle_feedbacks]) / len(
circle_feedbacks
)
if len(circle_feedbacks):
avg = sum([fb.satisfaction_average for fb in circle_feedbacks]) / len(
circle_feedbacks
)
else:
avg = 0
return FeedbackStatisticsResponsesType(
_id=course_id, # noqa

View File

@ -8,12 +8,12 @@ from vbv_lernwelt.dashboard.tests.graphql.utils import (
add_course_session_user,
create_assignment,
create_assignment_completion,
create_circle,
create_course,
create_course_session,
create_course_session_group,
create_user,
create_circle,
create_performance_criteria_page,
create_user,
)