Fix self-evalution checkboxes
This commit is contained in:
parent
e230c0b8e5
commit
bdae082550
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { Circle } from '@/services/circle'
|
||||
import type { Circle } from '@/services/circle'
|
||||
import ItFullScreenModal from '@/components/ui/ItFullScreenModal.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import ItCheckbox from '@/components/ui/ItCheckbox.vue'
|
||||
import type { LearningContent, LearningSequence } from '@/types'
|
||||
import type { CourseCompletionStatus, LearningContent, LearningSequence } from '@/types'
|
||||
import { useCircleStore } from '@/stores/circle'
|
||||
import { computed } from 'vue'
|
||||
import _ from 'lodash'
|
||||
|
|
@ -14,7 +14,11 @@ const props = defineProps<{
|
|||
const circleStore = useCircleStore()
|
||||
|
||||
function toggleCompleted(learningContent: LearningContent) {
|
||||
circleStore.markCompletion(learningContent, !learningContent.completed)
|
||||
let completionStatus: CourseCompletionStatus = 'success'
|
||||
if (learningContent.completion_status === 'success') {
|
||||
completionStatus = 'fail'
|
||||
}
|
||||
circleStore.markCompletion(learningContent, completionStatus)
|
||||
}
|
||||
|
||||
const someFinished = computed(() => {
|
||||
|
|
@ -36,7 +40,7 @@ const allFinished = computed(() => {
|
|||
const continueTranslationKeyTuple = computed(() => {
|
||||
if (props.learningSequence && circleStore.circle) {
|
||||
const lastFinished = _.findLast(circleStore.circle.flatLearningContents, (learningContent) => {
|
||||
return learningContent.completed
|
||||
return learningContent.completion_status === 'success'
|
||||
})
|
||||
|
||||
if (!lastFinished) {
|
||||
|
|
@ -91,7 +95,7 @@ const learningSequenceBorderClass = computed(() => {
|
|||
class="flex gap-4 pb-3 lg:pb-6"
|
||||
>
|
||||
<ItCheckbox
|
||||
:modelValue="learningContent.completed"
|
||||
:modelValue="learningContent.completion_status === 'success'"
|
||||
:onToggle="() => toggleCompleted(learningContent)"
|
||||
:data-cy="`${learningContent.slug}`"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import * as log from 'loglevel'
|
||||
import { computed, reactive } from 'vue'
|
||||
import { useCircleStore } from '@/stores/circle'
|
||||
import { LearningUnit } from '@/types'
|
||||
import type { LearningUnit } from '@/types'
|
||||
|
||||
log.debug('LearningContent.vue setup')
|
||||
|
||||
|
|
@ -62,24 +62,24 @@ function handleContinue() {
|
|||
|
||||
<div class="mt-4 lg:mt-8 flex flex-col lg:flex-row justify-between gap-6">
|
||||
<button
|
||||
@click="circleStore.markCompletion(currentQuestion, true)"
|
||||
@click="circleStore.markCompletion(currentQuestion, 'success')"
|
||||
class="flex-1 inline-flex items-center text-left p-4 border"
|
||||
:class="{
|
||||
'border-green-500': currentQuestion.completed,
|
||||
'border-2': currentQuestion.completed,
|
||||
'border-gray-500': !currentQuestion.completed,
|
||||
'border-green-500': currentQuestion.completion_status === 'success',
|
||||
'border-2': currentQuestion.completion_status === 'success',
|
||||
'border-gray-500': currentQuestion.completion_status !== 'success',
|
||||
}"
|
||||
>
|
||||
<it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy>
|
||||
<span class="font-bold text-xl"> Ja, ich kann das. </span>
|
||||
</button>
|
||||
<button
|
||||
@click="circleStore.markCompletion(currentQuestion, false)"
|
||||
@click="circleStore.markCompletion(currentQuestion, 'fail')"
|
||||
class="flex-1 inline-flex items-center text-left p-4 border"
|
||||
:class="{
|
||||
'border-orange-500': currentQuestion.completed === false,
|
||||
'border-2': currentQuestion.completed === false,
|
||||
'border-gray-500': currentQuestion.completed === true || currentQuestion.completed === undefined,
|
||||
'border-orange-500': currentQuestion.completion_status === 'fail',
|
||||
'border-2': currentQuestion.completion_status === 'fail',
|
||||
'border-gray-500': currentQuestion.completion_status !== 'fail'
|
||||
}"
|
||||
>
|
||||
<it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import type {
|
||||
CircleChild,
|
||||
CircleCompletion,
|
||||
CircleGoal,
|
||||
CircleJobSituation,
|
||||
CourseCompletion,
|
||||
CourseCompletionStatus,
|
||||
CourseWagtailPage,
|
||||
LearningContent,
|
||||
LearningSequence,
|
||||
LearningUnit,
|
||||
LearningUnitQuestion,
|
||||
CourseWagtailPage,
|
||||
} from '@/types'
|
||||
import type { LearningPath } from '@/services/learningPath'
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ function _createEmptyLearningUnit(parentLearningSequence: LearningSequence): Lea
|
|||
parentLearningSequence: parentLearningSequence,
|
||||
children: [],
|
||||
last: true,
|
||||
completion_status: 'unknown',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +114,7 @@ export function parseLearningSequences (circle: Circle, children: CircleChild[])
|
|||
export class Circle implements CourseWagtailPage {
|
||||
readonly type = 'learnpath.Circle';
|
||||
readonly learningSequences: LearningSequence[];
|
||||
readonly completed: boolean;
|
||||
completion_status: CourseCompletionStatus = 'unknown'
|
||||
|
||||
nextCircle?: Circle;
|
||||
previousCircle?: Circle;
|
||||
|
|
@ -129,7 +131,6 @@ export class Circle implements CourseWagtailPage {
|
|||
public readonly parentLearningPath?: LearningPath,
|
||||
) {
|
||||
this.learningSequences = parseLearningSequences(this, this.children);
|
||||
this.completed = false;
|
||||
}
|
||||
|
||||
public static fromJson(json: any, learningPath?: LearningPath): Circle {
|
||||
|
|
@ -187,7 +188,7 @@ export class Circle implements CourseWagtailPage {
|
|||
public someFinishedInLearningSequence(translationKey: string): boolean {
|
||||
if (translationKey) {
|
||||
return this.flatChildren.filter((lc) => {
|
||||
return lc.completed && lc.parentLearningSequence?.translation_key === translationKey;
|
||||
return lc.completion_status === 'success' && lc.parentLearningSequence?.translation_key === translationKey;
|
||||
}).length > 0;
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +198,7 @@ export class Circle implements CourseWagtailPage {
|
|||
public allFinishedInLearningSequence(translationKey: string): boolean {
|
||||
if (translationKey) {
|
||||
const finishedContents = this.flatChildren.filter((lc) => {
|
||||
return lc.completed && lc.parentLearningSequence?.translation_key === translationKey;
|
||||
return lc.completion_status === 'success' && lc.parentLearningSequence?.translation_key === translationKey;
|
||||
}).length;
|
||||
|
||||
const totalContents = this.flatChildren.filter((lc) => {
|
||||
|
|
@ -209,15 +210,15 @@ export class Circle implements CourseWagtailPage {
|
|||
return false;
|
||||
}
|
||||
|
||||
public parseCompletionData(completionData: CircleCompletion[]) {
|
||||
public parseCompletionData(completionData: CourseCompletion[]) {
|
||||
this.flatChildren.forEach((page) => {
|
||||
const pageIndex = completionData.findIndex((e) => {
|
||||
return e.page_key === page.translation_key;
|
||||
});
|
||||
if (pageIndex >= 0) {
|
||||
page.completed = completionData[pageIndex].completed;
|
||||
page.completion_status = completionData[pageIndex].completion_status;
|
||||
} else {
|
||||
page.completed = undefined;
|
||||
page.completion_status = 'unknown';
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
import * as _ from 'lodash'
|
||||
|
||||
import type { CircleCompletion, LearningContent, LearningPathChild, CourseWagtailPage, Topic } from '@/types'
|
||||
import type {
|
||||
CourseCompletion,
|
||||
CourseCompletionStatus,
|
||||
CourseWagtailPage,
|
||||
LearningContent,
|
||||
LearningPathChild,
|
||||
Topic,
|
||||
} from '@/types'
|
||||
import { Circle } from '@/services/circle'
|
||||
|
||||
function getLastCompleted(learningPathKey: string, completionData: CircleCompletion[]) {
|
||||
return _.orderBy(completionData, ['updated_at'], 'desc').find((c: CircleCompletion) => {
|
||||
return c.completed && c.learning_path_key === learningPathKey && c.page_type === 'learnpath.LearningContent'
|
||||
function getLastCompleted(courseId: number, completionData: CourseCompletion[]) {
|
||||
return _.orderBy(completionData, ['updated_at'], 'desc').find((c: CourseCompletion) => {
|
||||
return c.completion_status === 'success' && c.course === courseId && c.page_type === 'learnpath.LearningContent'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -14,9 +21,10 @@ export class LearningPath implements CourseWagtailPage {
|
|||
public topics: Topic[]
|
||||
public circles: Circle[]
|
||||
public nextLearningContent?: LearningContent
|
||||
readonly completion_status: CourseCompletionStatus = 'unknown'
|
||||
|
||||
public static fromJson(json: any, completionData: CircleCompletion[]): LearningPath {
|
||||
return new LearningPath(json.id, json.slug, json.title, json.translation_key, json.children, completionData)
|
||||
public static fromJson(json: any, completionData: CourseCompletion[]): LearningPath {
|
||||
return new LearningPath(json.id, json.slug, json.title, json.translation_key, json.course.id, json.children, completionData)
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
|
@ -24,8 +32,9 @@ export class LearningPath implements CourseWagtailPage {
|
|||
public readonly slug: string,
|
||||
public readonly title: string,
|
||||
public readonly translation_key: string,
|
||||
public readonly courseId: number,
|
||||
public children: LearningPathChild[],
|
||||
completionData?: any
|
||||
completionData?: CourseCompletion[]
|
||||
) {
|
||||
// parse children
|
||||
this.topics = []
|
||||
|
|
@ -42,7 +51,9 @@ export class LearningPath implements CourseWagtailPage {
|
|||
}
|
||||
if (page.type === 'learnpath.Circle') {
|
||||
const circle = Circle.fromJson(page, this)
|
||||
circle.parseCompletionData(completionData)
|
||||
if (completionData) {
|
||||
circle.parseCompletionData(completionData)
|
||||
}
|
||||
if (topic) {
|
||||
topic.circles.push(circle)
|
||||
}
|
||||
|
|
@ -59,17 +70,21 @@ export class LearningPath implements CourseWagtailPage {
|
|||
this.topics.push(topic)
|
||||
}
|
||||
|
||||
this.calcNextLearningContent(completionData)
|
||||
if (completionData) {
|
||||
this.calcNextLearningContent(completionData)
|
||||
}
|
||||
}
|
||||
|
||||
public calcNextLearningContent(completionData: CircleCompletion[]): void {
|
||||
public calcNextLearningContent(completionData: CourseCompletion[]): void {
|
||||
this.nextLearningContent = undefined
|
||||
|
||||
const lastCompletedLearningContent = getLastCompleted(this.translation_key, completionData)
|
||||
const lastCompletedLearningContent = getLastCompleted(this.courseId, completionData)
|
||||
|
||||
if (lastCompletedLearningContent) {
|
||||
const lastCircle = this.circles.find(
|
||||
(circle) => circle.translation_key === lastCompletedLearningContent.circle_key
|
||||
(circle) => {
|
||||
return circle.flatLearningContents.find((learningContent) => learningContent.translation_key === lastCompletedLearningContent.page_key)
|
||||
}
|
||||
)
|
||||
if (lastCircle) {
|
||||
const lastLearningContent = lastCircle.flatLearningContents.find(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as log from 'loglevel'
|
|||
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import type { LearningContent, LearningUnit, LearningUnitQuestion } from '@/types'
|
||||
import type { CourseCompletionStatus, LearningContent, LearningUnit, LearningUnitQuestion } from '@/types'
|
||||
import type { Circle } from '@/services/circle'
|
||||
import { itPost } from '@/fetchHelpers'
|
||||
import { useLearningPathStore } from '@/stores/learningPath'
|
||||
|
|
@ -61,12 +61,12 @@ export const useCircleStore = defineStore({
|
|||
|
||||
return learningUnit
|
||||
},
|
||||
async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) {
|
||||
async markCompletion(page: LearningContent | LearningUnitQuestion, completion_status:CourseCompletionStatus='success') {
|
||||
try {
|
||||
page.completed = flag;
|
||||
const completionData = await itPost('/api/completion/circle/mark/', {
|
||||
page.completion_status = completion_status;
|
||||
const completionData = await itPost('/api/course/completion/mark/', {
|
||||
page_key: page.translation_key,
|
||||
completed: page.completed,
|
||||
completion_status: page.completion_status,
|
||||
});
|
||||
if (this.circle) {
|
||||
this.circle.parseCompletionData(completionData);
|
||||
|
|
@ -100,10 +100,10 @@ export const useCircleStore = defineStore({
|
|||
},
|
||||
calcSelfEvaluationStatus(learningUnit: LearningUnit) {
|
||||
if (learningUnit.children.length > 0) {
|
||||
if (learningUnit.children.every((q) => q.completed)) {
|
||||
if (learningUnit.children.every((q) => q.completion_status === 'success')) {
|
||||
return true;
|
||||
}
|
||||
if (learningUnit.children.some((q) => q.completed !== undefined)) {
|
||||
if (learningUnit.children.some((q) => q.completion_status === 'fail')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ export const useCircleStore = defineStore({
|
|||
},
|
||||
continueFromLearningContent(currentLearningContent: LearningContent) {
|
||||
if (currentLearningContent) {
|
||||
this.markCompletion(currentLearningContent, true);
|
||||
this.markCompletion(currentLearningContent, 'success');
|
||||
|
||||
const nextLearningContent = currentLearningContent.nextLearningContent;
|
||||
const currentParent = currentLearningContent.parentLearningUnit;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export const useLearningPathStore = defineStore({
|
|||
return this.learningPath;
|
||||
}
|
||||
const learningPathData = await itGet(`/api/course/page/${slug}/`);
|
||||
const completionData = await itGet(`/api/completion/learning_path/${learningPathData.translation_key}/`);
|
||||
const completionData = await itGet(`/api/course/completion/${learningPathData.course.id}/`);
|
||||
|
||||
if (!learningPathData) {
|
||||
throw `No learning path found with: ${slug}`;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
import type { Circle } from '@/services/circle'
|
||||
|
||||
export type LearningContentType = 'assignment' | 'book' | 'document' |
|
||||
'exercise' | 'media_library' | 'online_training' |
|
||||
'resource' | 'test' | 'video';
|
||||
export type CourseCompletionStatus = 'unknown' | 'fail' | 'success'
|
||||
|
||||
export type LearningContentType =
|
||||
| 'assignment'
|
||||
| 'book'
|
||||
| 'document'
|
||||
| 'exercise'
|
||||
| 'media_library'
|
||||
| 'online_training'
|
||||
| 'resource'
|
||||
| 'test'
|
||||
| 'video'
|
||||
|
||||
export interface LearningContentBlock {
|
||||
type: LearningContentType
|
||||
|
|
@ -111,7 +120,7 @@ export interface CourseWagtailPage {
|
|||
readonly title: string;
|
||||
readonly slug: string;
|
||||
readonly translation_key: string;
|
||||
completed?: boolean;
|
||||
completion_status: CourseCompletionStatus;
|
||||
}
|
||||
|
||||
export interface LearningContent extends CourseWagtailPage {
|
||||
|
|
@ -163,17 +172,17 @@ export interface Topic extends CourseWagtailPage {
|
|||
|
||||
export type LearningPathChild = Topic | WagtailCircle;
|
||||
|
||||
export interface CircleCompletion {
|
||||
export interface CourseCompletion {
|
||||
id: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
user: number;
|
||||
page_key: string;
|
||||
page_type: string;
|
||||
circle_key: string;
|
||||
learning_path_key: string;
|
||||
completed: boolean;
|
||||
json_data: any;
|
||||
page_slug: string;
|
||||
course: number;
|
||||
completion_status: CourseCompletionStatus;
|
||||
additional_json_data: any;
|
||||
}
|
||||
|
||||
export interface CircleDiagramData {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { onMounted, reactive, watch } from 'vue'
|
|||
import { useCircleStore } from '@/stores/circle'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import LearningContent from '@/components/circle/LearningContent.vue'
|
||||
import { LearningContent as LearningContentType } from '@/types'
|
||||
import type { LearningContent as LearningContentType } from '@/types'
|
||||
|
||||
log.debug('LearningContentView created')
|
||||
|
||||
|
|
|
|||
|
|
@ -50,11 +50,6 @@ urlpatterns = [
|
|||
path(r"api/course/completion/mark/", mark_course_completion, name="mark_course_completion"),
|
||||
path(r"api/course/completion/<course_id>/", request_course_completion, name="request_course_completion"),
|
||||
|
||||
# completion
|
||||
# path(r"api/completion/circle/<uuid:circle_key>/", request_circle_completion, name="request_circle_completion"),
|
||||
# path(r"api/completion/learning_path/<uuid:learning_path_key>/", request_learning_path_completion, name="request_learning_path_completion"),
|
||||
# path(r"api/completion/circle/mark/", mark_circle_completion, name="mark_circle_completion"),
|
||||
|
||||
# testing and debug
|
||||
path('server/raise_error/', user_passes_test(lambda u: u.is_superuser, login_url='/login/')(raise_example_error), ),
|
||||
path("server/checkratelimit/", check_rate_limit),
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
|
|||
|
||||
|
||||
class LearningPath(Page):
|
||||
# PageChooserPanel('related_page', 'demo.PublisherPage'),
|
||||
|
||||
content_panels = Page.content_panels
|
||||
subpage_types = ['learnpath.Circle', 'learnpath.Topic']
|
||||
parent_page_types = ['course.CoursePage']
|
||||
|
|
@ -35,7 +33,7 @@ class LearningPath(Page):
|
|||
@classmethod
|
||||
def get_serializer_class(cls):
|
||||
return get_it_serializer_class(
|
||||
cls, ['id', 'title', 'slug', 'type', 'translation_key', 'children']
|
||||
cls, ['id', 'title', 'slug', 'type', 'translation_key', 'children', 'course']
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue