Merge branch 'feature/refactor-typescript' into develop
This commit is contained in:
commit
4635435ce3
|
|
@ -54,6 +54,7 @@ pipelines:
|
|||
- npm install
|
||||
- npm run prettier:check
|
||||
- npm run lint
|
||||
- npm run typecheck
|
||||
- step:
|
||||
name: cypress tests
|
||||
max-time: 45
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.6.7",
|
||||
"axios": "^0.26.1",
|
||||
"d3": "^7.6.1",
|
||||
"lodash": "^4.17.21",
|
||||
"loglevel": "^1.8.0",
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ function learninPathSlug(): string {
|
|||
}
|
||||
|
||||
function handleDropdownSelect(data: DropdownData) {
|
||||
log.debug("Selected action:", data.action);
|
||||
switch (data.action) {
|
||||
case "settings":
|
||||
router.push("/profile");
|
||||
|
|
@ -76,8 +75,7 @@ onMounted(() => {
|
|||
log.debug("MainNavigationBar mounted");
|
||||
});
|
||||
|
||||
const profileDropdownData: DropdownListItem[][] = [
|
||||
[
|
||||
const profileDropdownData: DropdownListItem[] = [
|
||||
{
|
||||
title: "Kontoeinstellungen",
|
||||
icon: IconSettings as Component,
|
||||
|
|
@ -85,8 +83,6 @@ const profileDropdownData: DropdownListItem[][] = [
|
|||
action: "settings",
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
title: "Abmelden",
|
||||
icon: IconLogout as Component,
|
||||
|
|
@ -94,7 +90,6 @@ const profileDropdownData: DropdownListItem[][] = [
|
|||
action: "logout",
|
||||
},
|
||||
},
|
||||
],
|
||||
];
|
||||
</script>
|
||||
|
||||
|
|
@ -102,7 +97,6 @@ const profileDropdownData: DropdownListItem[][] = [
|
|||
<div>
|
||||
<Teleport to="body">
|
||||
<MobileMenu
|
||||
:user-store="userStore"
|
||||
:show="state.showMenu"
|
||||
:learning-path-slug="learninPathSlug()"
|
||||
:learning-path-name="learningPathName()"
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
import IconLogout from "@/components/icons/IconLogout.vue";
|
||||
import IconSettings from "@/components/icons/IconSettings.vue";
|
||||
import ItFullScreenModal from "@/components/ui/ItFullScreenModal.vue";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean;
|
||||
userStore: object;
|
||||
learningPathName: string;
|
||||
learningPathSlug: string;
|
||||
}>();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ interface Props {
|
|||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
criteria: undefined,
|
||||
showState: false,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,14 @@ import * as _ from "lodash";
|
|||
import * as log from "loglevel";
|
||||
import { computed, onMounted } from "vue";
|
||||
|
||||
// @ts-ignore
|
||||
import colors from "@/colors.json";
|
||||
import type { LearningSequence } from "@/types";
|
||||
import type { DefaultArcObject } from "d3";
|
||||
|
||||
const circleStore = useCircleStore();
|
||||
|
||||
function someFinished(learningSequence) {
|
||||
function someFinished(learningSequence: LearningSequence) {
|
||||
if (circleStore.circle) {
|
||||
return circleStore.circle.someFinishedInLearningSequence(
|
||||
learningSequence.translation_key
|
||||
|
|
@ -18,7 +21,7 @@ function someFinished(learningSequence) {
|
|||
return false;
|
||||
}
|
||||
|
||||
function allFinished(learningSequence) {
|
||||
function allFinished(learningSequence: LearningSequence) {
|
||||
if (circleStore.circle) {
|
||||
return circleStore.circle.allFinishedInLearningSequence(
|
||||
learningSequence.translation_key
|
||||
|
|
@ -32,6 +35,17 @@ onMounted(async () => {
|
|||
render();
|
||||
});
|
||||
|
||||
interface CirclePie extends d3.PieArcDatum<number> {
|
||||
title: string;
|
||||
icon: string;
|
||||
slug: string;
|
||||
translation_key: string;
|
||||
arrowStartAngle: number;
|
||||
arrowEndAngle: number;
|
||||
someFinished: boolean;
|
||||
allFinished: boolean;
|
||||
}
|
||||
|
||||
const pieData = computed(() => {
|
||||
const circle = circleStore.circle;
|
||||
console.log("initial of compute pie data ", circle);
|
||||
|
|
@ -41,37 +55,63 @@ const pieData = computed(() => {
|
|||
|
||||
const pieWeights = new Array(Math.max(circle.learningSequences.length, 1)).fill(1);
|
||||
const pieGenerator = d3.pie();
|
||||
let angles = pieGenerator(pieWeights);
|
||||
_.forEach(angles, (pie) => {
|
||||
const thisLearningSequence = circle.learningSequences[parseInt(pie.index)];
|
||||
pie.title = thisLearningSequence.title;
|
||||
pie.icon = thisLearningSequence.icon;
|
||||
pie.startAngle = pie.startAngle + Math.PI;
|
||||
pie.endAngle = pie.endAngle + Math.PI;
|
||||
pie.arrowStartAngle = pie.endAngle + (pie.startAngle - pie.endAngle) / 2;
|
||||
pie.arrowEndAngle = pie.startAngle + (pie.startAngle - pie.endAngle) / 2;
|
||||
pie.translation_key = thisLearningSequence.translation_key;
|
||||
pie.slug = thisLearningSequence.slug;
|
||||
pie.someFinished = someFinished(thisLearningSequence);
|
||||
pie.allFinished = allFinished(thisLearningSequence);
|
||||
const angles = pieGenerator(pieWeights);
|
||||
let result = angles.map((angle) => {
|
||||
const thisLearningSequence = circle.learningSequences[angle.index];
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
startAngle: angle.startAngle + Math.PI,
|
||||
endAngle: angle.endAngle + Math.PI,
|
||||
..._.pick(thisLearningSequence, ["title", "icon", "translation_key", "slug"]),
|
||||
arrowStartAngle: angle.endAngle + (angle.startAngle - angle.endAngle) / 2,
|
||||
arrowEndAngle: angle.startAngle + (angle.startAngle - angle.endAngle) / 2,
|
||||
someFinished: someFinished(thisLearningSequence),
|
||||
allFinished: allFinished(thisLearningSequence),
|
||||
},
|
||||
angle
|
||||
);
|
||||
});
|
||||
angles = angles.reverse();
|
||||
return angles;
|
||||
result = result.reverse();
|
||||
return result as CirclePie[];
|
||||
}
|
||||
return {};
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const width = 450;
|
||||
const height = 450;
|
||||
const radius = Math.min(width, height) / 2.4;
|
||||
|
||||
function render() {
|
||||
const arrowStrokeWidth = 2;
|
||||
function getColor(d: CirclePie) {
|
||||
let color = colors.gray[300];
|
||||
if (d.someFinished) {
|
||||
color = colors.sky[500];
|
||||
}
|
||||
if (d.allFinished) {
|
||||
color = colors.green[500];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
function getHoverColor(d: CirclePie) {
|
||||
let color = colors.gray[200];
|
||||
if (d.someFinished) {
|
||||
color = colors.sky[400];
|
||||
}
|
||||
if (d.allFinished) {
|
||||
color = colors.green[400];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
function render() {
|
||||
const svg = d3.select(".circle-visualization");
|
||||
// Clean svg before adding new stuff.
|
||||
svg.selectAll("*").remove();
|
||||
|
||||
if (pieData.value) {
|
||||
const arrowStrokeWidth = 2;
|
||||
// Append marker as definition to the svg
|
||||
svg
|
||||
.attr("viewBox", `0 0 ${width} ${height}`)
|
||||
|
|
@ -94,28 +134,6 @@ function render() {
|
|||
.append("g")
|
||||
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
|
||||
|
||||
function getColor(d) {
|
||||
let color = colors.gray[300];
|
||||
if (d.someFinished) {
|
||||
color = colors.sky[500];
|
||||
}
|
||||
if (d.allFinished) {
|
||||
color = colors.green[500];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
function getHoverColor(d) {
|
||||
let color = colors.gray[200];
|
||||
if (d.someFinished) {
|
||||
color = colors.sky[400];
|
||||
}
|
||||
if (d.allFinished) {
|
||||
color = colors.green[400];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
// Generate the pie diagram wede
|
||||
const wedgeGenerator = d3
|
||||
.arc()
|
||||
|
|
@ -141,6 +159,7 @@ function render() {
|
|||
.transition()
|
||||
.duration(200)
|
||||
.attr("fill", (d) => {
|
||||
// @ts-ignore
|
||||
return getHoverColor(d);
|
||||
});
|
||||
})
|
||||
|
|
@ -149,6 +168,7 @@ function render() {
|
|||
.transition()
|
||||
.duration(200)
|
||||
.attr("fill", (d) => {
|
||||
// @ts-ignore
|
||||
return getColor(d);
|
||||
});
|
||||
})
|
||||
|
|
@ -164,6 +184,7 @@ function render() {
|
|||
return getColor(d);
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
learningSequences.append("path").attr("d", wedgeGenerator);
|
||||
|
||||
const learningSequenceText = learningSequences
|
||||
|
|
@ -174,7 +195,7 @@ function render() {
|
|||
return d.title;
|
||||
})
|
||||
.attr("transform", function (d) {
|
||||
let translate = wedgeGenerator.centroid(d);
|
||||
let translate = wedgeGenerator.centroid(d as unknown as DefaultArcObject);
|
||||
translate = [translate[0], translate[1] + 20];
|
||||
return "translate(" + translate + ")";
|
||||
})
|
||||
|
|
@ -191,7 +212,7 @@ function render() {
|
|||
.attr("width", iconWidth)
|
||||
.attr("height", iconWidth)
|
||||
.attr("transform", function (d) {
|
||||
let translate = wedgeGenerator.centroid(d);
|
||||
let translate = wedgeGenerator.centroid(d as unknown as DefaultArcObject);
|
||||
translate = [translate[0] - iconWidth / 2, translate[1] - iconWidth];
|
||||
return "translate(" + translate + ")";
|
||||
})
|
||||
|
|
@ -204,10 +225,10 @@ function render() {
|
|||
.outerRadius(arrowRadius + arrowStrokeWidth)
|
||||
.padAngle(20 / 360)
|
||||
.startAngle((d) => {
|
||||
return d.arrowStartAngle;
|
||||
return (d as unknown as CirclePie).arrowStartAngle;
|
||||
})
|
||||
.endAngle((d) => {
|
||||
return d.arrowEndAngle;
|
||||
return (d as unknown as CirclePie).arrowEndAngle;
|
||||
});
|
||||
|
||||
const arrows = g
|
||||
|
|
@ -224,10 +245,13 @@ function render() {
|
|||
};
|
||||
|
||||
const all_arows = g.selectAll(".arrow");
|
||||
// @ts-ignore
|
||||
all_arows.last().remove();
|
||||
|
||||
//Draw arrow paths
|
||||
// @ts-ignore
|
||||
arrows.append("path").attr("fill", colors.gray[500]).attr("d", arrow);
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const emit = defineEmits(["back", "next"]);
|
|||
<button
|
||||
type="button"
|
||||
class="btn-text inline-flex items-center px-3 py-2"
|
||||
data-cy="close-learning-content"
|
||||
@click="$emit('back')"
|
||||
>
|
||||
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { LearningContentType } from "@/types";
|
||||
import { learningContentTypesToName } from "@/utils/typeMaps";
|
||||
import { learningContentTypeData } from "@/utils/typeMaps";
|
||||
|
||||
const props = defineProps<{
|
||||
learningContentType: LearningContentType;
|
||||
|
|
@ -11,39 +11,12 @@ const props = defineProps<{
|
|||
<div
|
||||
class="flex bg-gray-200 rounded-full px-2.5 py-0.5 gap-2 items-center w-min h-min"
|
||||
>
|
||||
<it-icon-lc-assignment
|
||||
v-if="props.learningContentType === 'assignment'"
|
||||
<component
|
||||
:is="learningContentTypeData(props.learningContentType).icon"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<it-icon-lc-exercise
|
||||
v-else-if="props.learningContentType === 'exercise'"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<it-icon-lc-book v-else-if="props.learningContentType === 'book'" class="w-6 h-6" />
|
||||
<it-icon-lc-video
|
||||
v-else-if="props.learningContentType === 'video'"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<it-icon-lc-media-library
|
||||
v-else-if="props.learningContentType === 'media_library'"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<it-icon-lc-test v-else-if="props.learningContentType === 'test'" class="w-6 h-6" />
|
||||
<it-icon-lc-online-training
|
||||
v-else-if="props.learningContentType === 'online_training'"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<it-icon-lc-resource
|
||||
v-else-if="props.learningContentType === 'resource'"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<it-icon-lc-resource
|
||||
v-else-if="props.learningContentType === 'document'"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<it-icon-lc-document v-else class="w-6 h-6" />
|
||||
></component>
|
||||
<p class="whitespace-nowrap">
|
||||
{{ learningContentTypesToName.get(props.learningContentType) }}
|
||||
{{ learningContentTypeData(props.learningContentType).title }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -121,8 +121,8 @@ const learningSequenceBorderClass = computed(() => {
|
|||
<span class="flex gap-4 items-center xl:h-10">
|
||||
<button
|
||||
class="cursor-pointer w-full sm:w-auto text-left"
|
||||
@click.stop="circleStore.openLearningContent(learningContent)"
|
||||
:data-cy="`${learningContent.slug}`"
|
||||
@click.stop="circleStore.openLearningContent(learningContent)"
|
||||
>
|
||||
{{ learningContent.title }}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
<div>
|
||||
<h4 class="mb-2 text-bold">{{ title }}</h4>
|
||||
<p class="mb-2">{{ description }}</p>
|
||||
<media-link :to="url" :blank="openWindow" class="link">
|
||||
<MediaLink :to="url" :blank="openWindow" class="link">
|
||||
<span class="inline">{{ linkText }}</span>
|
||||
</media-link>
|
||||
</MediaLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import { computed } from "vue";
|
|||
import { RouterLink } from "vue-router";
|
||||
|
||||
const props = defineProps({
|
||||
...RouterLink.props, // @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
...RouterLink.props,
|
||||
blank: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ defineEmits(["update:modelValue"]);
|
|||
'opacity-50': disabled,
|
||||
'cursor-not-allowed': disabled,
|
||||
}"
|
||||
@click="$emit('update:modelValue', !modelValue)"
|
||||
class="w-8 h-8 cursor-pointer"
|
||||
@click="$emit('update:modelValue', !modelValue)"
|
||||
>
|
||||
<button
|
||||
v-if="modelValue"
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ import type { DropdownListItem } from "@/types";
|
|||
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue";
|
||||
|
||||
const props = defineProps<{
|
||||
buttonClasses: [string];
|
||||
listItems: [[DropdownListItem]];
|
||||
buttonClasses: [string] | [];
|
||||
listItems: DropdownListItem[];
|
||||
align: "left" | "right";
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "select", data: object): void;
|
||||
(e: "select", data: any): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
|
@ -33,8 +33,7 @@ const emit = defineEmits<{
|
|||
class="absolute mt-2 px-6 w-56 w-max-full origin-top-right divide-y divide-gray-500 bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
:class="[align === 'left' ? 'left-0' : 'right-0']"
|
||||
>
|
||||
<div v-for="section in listItems" :key="section" class="">
|
||||
<div v-for="item in section" :key="item" class="px-1 py-1">
|
||||
<div v-for="item in listItems" :key="item.title" class="px-1 py-1">
|
||||
<MenuItem>
|
||||
<button
|
||||
class="text-black group flex w-full items-center px-0 py-2 text-sm"
|
||||
|
|
@ -47,7 +46,6 @@ const emit = defineEmits<{
|
|||
</button>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</div>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</Menu>
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@ export const itPost = (url: RequestInfo, data: unknown, options: RequestInit = {
|
|||
"Content-Type": "application/json;charset=UTF-8",
|
||||
},
|
||||
options?.headers
|
||||
);
|
||||
|
||||
if (options?.headers) {
|
||||
delete options.headers;
|
||||
}
|
||||
) as HeadersInit;
|
||||
|
||||
options = Object.assign(
|
||||
{
|
||||
|
|
@ -43,7 +39,6 @@ export const itPost = (url: RequestInfo, data: unknown, options: RequestInit = {
|
|||
options
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
options.headers["X-CSRFToken"] = getCookieValue("csrftoken");
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export function setI18nLanguage(i18n: any, locale: string) {
|
|||
*
|
||||
* axios.defaults.headers.common['Accept-Language'] = locale
|
||||
*/
|
||||
document.querySelector("html").setAttribute("lang", locale);
|
||||
document.querySelector("html")?.setAttribute("lang", locale);
|
||||
}
|
||||
|
||||
export async function loadLocaleMessages(i18n: any, locale: any) {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,11 @@ const userStore = useUserStore();
|
|||
<form
|
||||
class="bg-white p-4 lg:p-8"
|
||||
@submit.prevent="
|
||||
userStore.handleLogin(state.username, state.password, route.query.next)
|
||||
userStore.handleLogin(
|
||||
state.username,
|
||||
state.password,
|
||||
route.query.next as string
|
||||
)
|
||||
"
|
||||
>
|
||||
<div class="mb-4">
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ const state = reactive({
|
|||
});
|
||||
|
||||
const dropdownData = [
|
||||
[
|
||||
{
|
||||
title: "Option 1",
|
||||
icon: IconLogout,
|
||||
|
|
@ -35,13 +34,11 @@ const dropdownData = [
|
|||
},
|
||||
{
|
||||
title: "Option 2",
|
||||
icon: null,
|
||||
icon: IconLogout,
|
||||
data: {
|
||||
test: 12,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
title: "Option 3",
|
||||
icon: IconSettings,
|
||||
|
|
@ -49,7 +46,6 @@ const dropdownData = [
|
|||
amount: 34,
|
||||
},
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
// TODO: die CSS-Klasse für die Farben wird hier in der StyleGuideView.vue generiert.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const competenceStore = useCompetenceStore();
|
|||
<div class="container-large">
|
||||
<nav class="py-4 lg:pb-8">
|
||||
<router-link
|
||||
v-if="competenceStore.competenceProfilePage"
|
||||
class="btn-text inline-flex items-center pl-0"
|
||||
:to="competenceStore.competenceProfilePage?.frontend_url"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
|||
import { useCompetenceStore } from "@/stores/competence";
|
||||
import type { CourseCompletionStatus } from "@/types";
|
||||
import * as log from "loglevel";
|
||||
import { computed, Ref, ref } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
log.debug("CompetencesMainView created");
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ let competencePage: CompetencePage | undefined;
|
|||
const findCriteria = () => {
|
||||
for (const page of competenceStore.competenceProfilePage
|
||||
?.children as CompetencePage[]) {
|
||||
for (let criteria of page.children) {
|
||||
for (const criteria of page.children) {
|
||||
if (criteria.slug === route.params["criteriaSlug"]) {
|
||||
currentQuestion = criteria;
|
||||
competencePage = page;
|
||||
|
|
@ -63,7 +63,7 @@ findCriteria();
|
|||
@back="router.back()"
|
||||
@next="router.back()"
|
||||
>
|
||||
<div class="container-medium">
|
||||
<div v-if="currentQuestion" class="container-medium">
|
||||
<div class="mt-4 lg:mt-8 p-6 lg:p-12 border">
|
||||
<h2 class="heading-2">
|
||||
{{ currentQuestion.competence_id }} {{ currentQuestion.title }}
|
||||
|
|
|
|||
|
|
@ -52,10 +52,12 @@ onMounted(async () => {
|
|||
const learningUnits = circleStore.circle?.learningSequences.flatMap(
|
||||
(ls) => ls.learningUnits
|
||||
);
|
||||
if (learningUnits) {
|
||||
wagtailPage = learningUnits.find((lu) => {
|
||||
return lu.slug.endsWith(slugEnd);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (wagtailPage) {
|
||||
document
|
||||
.getElementById(wagtailPage.slug)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import LinkCard from "@/components/mediaLibrary/LinkCard.vue";
|
||||
import MediaLink from "@/components/mediaLibrary/MediaLink.vue";
|
||||
import { useMediaLibraryStore } from "@/stores/mediaLibrary";
|
||||
import type { MediaBlockType } from "@/types";
|
||||
import * as log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
|
@ -33,28 +34,28 @@ const backLink = computed(() => {
|
|||
const maxCardItems = 4;
|
||||
const maxListItems = 6;
|
||||
|
||||
const displayAsCard = (itemType: string): boolean => {
|
||||
const displayAsCard = (itemType: MediaBlockType): boolean => {
|
||||
return itemType === "learn_media" || itemType === "relative_link";
|
||||
};
|
||||
|
||||
const hasMoreItems = (items: object[], maxItems: number): boolean => {
|
||||
function hasMoreItems<T>(items: T[], maxItems: number): boolean {
|
||||
return items.length > maxItems;
|
||||
};
|
||||
}
|
||||
|
||||
const getMaxDisplayItems = (items: object[], maxItems: number) => {
|
||||
function getMaxDisplayItems<T>(items: T[], maxItems: number) {
|
||||
return items.slice(0, maxItems);
|
||||
};
|
||||
}
|
||||
|
||||
const getMaxDisplayItemsForType = (itemType: string, items: object[]) => {
|
||||
function getMaxDisplayItemsForType<T>(itemType: MediaBlockType, items: T[]) {
|
||||
return displayAsCard(itemType)
|
||||
? getMaxDisplayItems(items, maxCardItems)
|
||||
: getMaxDisplayItems(items, maxListItems);
|
||||
};
|
||||
}
|
||||
|
||||
const hasMoreItemsForType = (itemType: string, items: object[]) => {
|
||||
function hasMoreItemsForType<T>(itemType: MediaBlockType, items: T[]) {
|
||||
const maxItems = displayAsCard(itemType) ? maxCardItems : maxListItems;
|
||||
return hasMoreItems(items, maxItems);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -65,7 +66,10 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
|
|||
<div class="bg-gray-200 pb-4 lg:pb-12">
|
||||
<div class="container-large">
|
||||
<nav class="py-4 lg:pb-8">
|
||||
<router-link class="btn-text inline-flex items-center pl-0" :to="backLink">
|
||||
<router-link
|
||||
class="btn-text inline-flex items-center pl-0"
|
||||
:to="(backLink as string)"
|
||||
>
|
||||
<it-icon-arrow-left />
|
||||
<span>zurück</span>
|
||||
</router-link>
|
||||
|
|
@ -93,7 +97,7 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
|
|||
<ul>
|
||||
<li
|
||||
v-for="item in mediaCategory.items"
|
||||
:key="item"
|
||||
:key="item.id"
|
||||
class="mb-2 flex items-center"
|
||||
>
|
||||
<it-icon-check class="h-8 w-8 text-sky-500 mr-4 flex-none"></it-icon-check>
|
||||
|
|
@ -141,13 +145,13 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
|
|||
/>
|
||||
<div v-else class="flex items-center justify-between border-b py-4">
|
||||
<h4 class="text-bold">{{ mediaItem.value.title }}</h4>
|
||||
<media-link
|
||||
<MediaLink
|
||||
:blank="mediaItem.value.open_window"
|
||||
:to="mediaItem.value.url"
|
||||
class="link"
|
||||
>
|
||||
{{ mediaItem.value.link_display_text }}
|
||||
</media-link>
|
||||
</MediaLink>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ const mediaList = computed(() => {
|
|||
return contentCollection.value.contents[0].type === "learn_media";
|
||||
}
|
||||
});
|
||||
console.log(learnMediaCollection);
|
||||
return learnMediaCollection?.value;
|
||||
}
|
||||
return undefined;
|
||||
|
|
@ -35,7 +34,7 @@ const mediaList = computed(() => {
|
|||
|
||||
<template>
|
||||
<div
|
||||
v-if="mediaCategory"
|
||||
v-if="mediaCategory && mediaStore.mediaLibraryPage && mediaList"
|
||||
class="fixed top-0 overflow-y-scroll bg-white h-full w-full"
|
||||
>
|
||||
<div class="bg-gray-200">
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { WagtailCircle } from "@/types";
|
||||
import { describe, it } from "vitest";
|
||||
import { Circle } from "../circle";
|
||||
import data from "./learning_path_json.json";
|
||||
|
|
@ -6,7 +7,7 @@ describe("Circle.parseJson", () => {
|
|||
it("can parse circle from api response", () => {
|
||||
const cirleData = data.children.find(
|
||||
(c) => c.slug === "test-lehrgang-lp-circle-analyse"
|
||||
);
|
||||
) as unknown as WagtailCircle;
|
||||
const circle = Circle.fromJson(cirleData, undefined);
|
||||
expect(circle.learningSequences.length).toBe(3);
|
||||
expect(circle.flatLearningContents.length).toBe(7);
|
||||
|
|
|
|||
|
|
@ -1,39 +1,59 @@
|
|||
{
|
||||
"id": 372,
|
||||
"id": 362,
|
||||
"title": "Test Lernpfad",
|
||||
"slug": "test-lehrgang-lp",
|
||||
"type": "learnpath.LearningPath",
|
||||
"translation_key": "42e559ca-970f-4a08-9e5e-63860585ee1e",
|
||||
"translation_key": "8a230aa1-075e-4ac1-a8d6-87642c4f33ba",
|
||||
"frontend_url": "/learn/test-lehrgang-lp",
|
||||
"children": [
|
||||
{
|
||||
"id": 373,
|
||||
"id": 363,
|
||||
"title": "Basis",
|
||||
"slug": "test-lehrgang-lp-topic-basis",
|
||||
"type": "learnpath.Topic",
|
||||
"translation_key": "d68c1544-cf22-4a59-a81c-8cb977440cd0",
|
||||
"translation_key": "d6e14156-2fb9-4f1b-83ce-6879e364f9a2",
|
||||
"frontend_url": "",
|
||||
"is_visible": false
|
||||
},
|
||||
{
|
||||
"id": 374,
|
||||
"id": 364,
|
||||
"title": "Basis",
|
||||
"slug": "test-lehrgang-lp-circle-basis",
|
||||
"type": "learnpath.Circle",
|
||||
"translation_key": "ec62a2af-6f74-4031-b971-c3287bbbc573",
|
||||
"translation_key": "8034e867-4b05-4509-a9bc-99f9f3619e88",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/basis",
|
||||
"children": [
|
||||
{
|
||||
"id": 375,
|
||||
"id": 365,
|
||||
"title": "Starten",
|
||||
"slug": "test-lehrgang-lp-circle-basis-ls-starten",
|
||||
"type": "learnpath.LearningSequence",
|
||||
"translation_key": "c5fdada9-036d-4516-a50f-6656a1c6b009",
|
||||
"translation_key": "868bc4cb-c5b5-423e-a890-433184cd06e0",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/basis#ls-starten",
|
||||
"icon": "it-icon-ls-start"
|
||||
},
|
||||
{
|
||||
"id": 376,
|
||||
"id": 366,
|
||||
"title": "Einf\u00fchrung",
|
||||
"slug": "test-lehrgang-lp-circle-basis-lu-einf\u00fchrung",
|
||||
"type": "learnpath.LearningUnit",
|
||||
"translation_key": "6b0a4794-9861-4ea4-b422-99261a4347a6",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/basis#lu-einf\u00fchrung",
|
||||
"evaluate_url": "/learn/test-lehrgang-lp/basis/evaluate/einf\u00fchrung",
|
||||
"course_category": {
|
||||
"id": 14,
|
||||
"title": "Allgemein",
|
||||
"general": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 367,
|
||||
"title": "Einf\u00fchrung",
|
||||
"slug": "test-lehrgang-lp-circle-basis-lc-einf\u00fchrung",
|
||||
"type": "learnpath.LearningContent",
|
||||
"translation_key": "01de5131-28ce-4b1f-805f-8643384bfd6b",
|
||||
"translation_key": "d1d1b923-f597-4de7-ac44-d02c2f0a1a59",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/basis/einf\u00fchrung",
|
||||
"minutes": 15,
|
||||
"contents": [
|
||||
{
|
||||
|
|
@ -42,24 +62,41 @@
|
|||
"description": "Beispiel Dokument",
|
||||
"url": null
|
||||
},
|
||||
"id": "bd05f721-3e9d-4a11-8fe2-7c04e2365f52"
|
||||
"id": "9f22d0b7-643a-4e97-816a-a41141befc95"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 377,
|
||||
"id": 368,
|
||||
"title": "Beenden",
|
||||
"slug": "test-lehrgang-lp-circle-basis-ls-beenden",
|
||||
"type": "learnpath.LearningSequence",
|
||||
"translation_key": "128c0162-025f-41be-9842-60016a77cdbc",
|
||||
"translation_key": "338208db-7c85-470e-872f-850e34747873",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/basis#ls-beenden",
|
||||
"icon": "it-icon-ls-end"
|
||||
},
|
||||
{
|
||||
"id": 378,
|
||||
"id": 369,
|
||||
"title": "Beenden",
|
||||
"slug": "test-lehrgang-lp-circle-basis-lu-beenden",
|
||||
"type": "learnpath.LearningUnit",
|
||||
"translation_key": "c14b63d6-3144-41fa-8a3c-2eada6ddd5ea",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/basis#lu-beenden",
|
||||
"evaluate_url": "/learn/test-lehrgang-lp/basis/evaluate/beenden",
|
||||
"course_category": {
|
||||
"id": 14,
|
||||
"title": "Allgemein",
|
||||
"general": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 370,
|
||||
"title": "Jetzt kann es losgehen!",
|
||||
"slug": "test-lehrgang-lp-circle-basis-lc-jetzt-kann-es-losgehen",
|
||||
"type": "learnpath.LearningContent",
|
||||
"translation_key": "271896b9-6082-4fd4-9d70-6093ec9cc6ea",
|
||||
"translation_key": "6920bcac-597b-462a-9458-32aa5dc8d3f7",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/basis/jetzt-kann-es-losgehen",
|
||||
"minutes": 30,
|
||||
"contents": [
|
||||
{
|
||||
|
|
@ -68,45 +105,130 @@
|
|||
"description": "Beispiel Dokument",
|
||||
"url": null
|
||||
},
|
||||
"id": "204fc13b-a9ae-40de-8e09-f1e922c4fdd9"
|
||||
"id": "1422a7c3-0a9a-4321-88a0-d82d0ed26ba2"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Basis",
|
||||
"job_situations": [],
|
||||
"goals": [],
|
||||
"experts": []
|
||||
"goal_description": "('In diesem Circle baust du deine Handlungskompetenzen f\u00fcr diese Themen aus:',)",
|
||||
"goals": [
|
||||
{
|
||||
"type": "goal",
|
||||
"value": "... hier ein Beispieltext f\u00fcr ein Ziel 1",
|
||||
"id": "38afbda4-7b7e-4f5c-88e8-c595c43e1659"
|
||||
},
|
||||
{
|
||||
"id": 379,
|
||||
"type": "goal",
|
||||
"value": "... hier ein Beispieltext f\u00fcr ein Ziel 2",
|
||||
"id": "4d00ac58-0499-4316-9af2-356c37dedc35"
|
||||
},
|
||||
{
|
||||
"type": "goal",
|
||||
"value": "... hier ein Beispieltext f\u00fcr ein Ziel 3",
|
||||
"id": "945eb104-8cc1-45cd-a07a-a4d3ec4f39a3"
|
||||
}
|
||||
],
|
||||
"job_situation_description": "Du triffst in diesem Circle auf die folgenden berufstypischen Handlungsfelder:",
|
||||
"job_situations": [
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Job Situation 1",
|
||||
"id": "02fb807a-0d07-4353-81ec-8b8b383954d7"
|
||||
},
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Job Situation 2",
|
||||
"id": "371952f6-5871-4bf1-b423-d3dab7371001"
|
||||
},
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Job Situation 3",
|
||||
"id": "116bfa7b-65e8-44a1-8c82-e8b05fd86a01"
|
||||
},
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Job Situation 4",
|
||||
"id": "08baf7dd-8801-4af9-8af8-714989775ddb"
|
||||
},
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Job Situation 5",
|
||||
"id": "93ade4b8-c4fb-4941-98c5-e58336fca4bb"
|
||||
},
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Job Situation 6",
|
||||
"id": "1fac4ee4-6d86-4e9e-9fa4-a99c6659bc8b"
|
||||
},
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Job Situation 7",
|
||||
"id": "06d1e273-dec8-4a0b-ae2c-2baeb7a19ec7"
|
||||
}
|
||||
],
|
||||
"experts": [
|
||||
{
|
||||
"type": "person",
|
||||
"value": {
|
||||
"first_name": "Patrizia",
|
||||
"last_name": "Mustermann",
|
||||
"email": "patrizia.mustermann@example.com",
|
||||
"photo": null,
|
||||
"biography": ""
|
||||
},
|
||||
"id": "83490f33-da54-4548-baac-af75ea36651e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 371,
|
||||
"title": "Beraten der Kunden",
|
||||
"slug": "test-lehrgang-lp-topic-beraten-der-kunden",
|
||||
"type": "learnpath.Topic",
|
||||
"translation_key": "91918780-75f8-4db3-8fb8-91b63f08b9b9",
|
||||
"translation_key": "728a2578-a22c-41df-9079-43a5318c5030",
|
||||
"frontend_url": "",
|
||||
"is_visible": true
|
||||
},
|
||||
{
|
||||
"id": 380,
|
||||
"id": 372,
|
||||
"title": "Analyse",
|
||||
"slug": "test-lehrgang-lp-circle-analyse",
|
||||
"type": "learnpath.Circle",
|
||||
"translation_key": "50f11be3-a56d-412d-be25-3d272fb5df40",
|
||||
"translation_key": "e429adf5-dd5d-4699-b471-40c782fb507e",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse",
|
||||
"children": [
|
||||
{
|
||||
"id": 381,
|
||||
"id": 373,
|
||||
"title": "Starten",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-ls-starten",
|
||||
"type": "learnpath.LearningSequence",
|
||||
"translation_key": "07ac0eb9-3671-4b62-8053-1d0c43a1f0fb",
|
||||
"translation_key": "40e977e0-3668-418d-b838-d3774a5cbe7d",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse#ls-starten",
|
||||
"icon": "it-icon-ls-start"
|
||||
},
|
||||
{
|
||||
"id": 382,
|
||||
"id": 374,
|
||||
"title": "Einf\u00fchrung",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lu-einf\u00fchrung",
|
||||
"type": "learnpath.LearningUnit",
|
||||
"translation_key": "badfd186-26c1-433e-90ad-8cac52eb599f",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse#lu-einf\u00fchrung",
|
||||
"evaluate_url": "/learn/test-lehrgang-lp/analyse/evaluate/einf\u00fchrung",
|
||||
"course_category": {
|
||||
"id": 14,
|
||||
"title": "Allgemein",
|
||||
"general": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 375,
|
||||
"title": "Einleitung Circle \"Analyse\"",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lc-einleitung-circle-analyse",
|
||||
"type": "learnpath.LearningContent",
|
||||
"translation_key": "00ed0ab2-fdb0-4ee6-a7d2-42a219b849a8",
|
||||
"translation_key": "5e8d6478-6287-4658-94c5-ecbd5d624962",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse/einleitung-circle-analyse",
|
||||
"minutes": 15,
|
||||
"contents": [
|
||||
{
|
||||
|
|
@ -115,24 +237,27 @@
|
|||
"description": "Beispiel Dokument",
|
||||
"url": null
|
||||
},
|
||||
"id": "892a9a4a-8e1e-4f7e-8c35-9bf3bbe5371b"
|
||||
"id": "8b7f183e-1879-4391-953f-52d9a621f435"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 383,
|
||||
"id": 376,
|
||||
"title": "Beobachten",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-ls-beobachten",
|
||||
"type": "learnpath.LearningSequence",
|
||||
"translation_key": "4cb08bc2-d101-43cc-b006-8f2bbb1a0579",
|
||||
"translation_key": "35df96df-2e8d-4f16-aee1-8d72990f63a0",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse#ls-beobachten",
|
||||
"icon": "it-icon-ls-watch"
|
||||
},
|
||||
{
|
||||
"id": 384,
|
||||
"id": 377,
|
||||
"title": "Fahrzeug",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lu-fahrzeug",
|
||||
"type": "learnpath.LearningUnit",
|
||||
"translation_key": "8f4afa40-c27e-48f7-a2d7-0e713479a55e",
|
||||
"translation_key": "405d42e4-ee10-4453-8e5f-82e49bb4d597",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse#lu-fahrzeug",
|
||||
"evaluate_url": "/learn/test-lehrgang-lp/analyse/evaluate/fahrzeug",
|
||||
"course_category": {
|
||||
"id": 15,
|
||||
"title": "Fahrzeug",
|
||||
|
|
@ -140,29 +265,32 @@
|
|||
},
|
||||
"children": [
|
||||
{
|
||||
"id": 397,
|
||||
"id": 391,
|
||||
"title": "Innerhalb des Handlungsfelds \u00abFahrzeug\u00bb bin ich f\u00e4hig, die Ziele und Pl\u00e4ne des Kunden zu ergr\u00fcnden (SOLL).",
|
||||
"slug": "test-lehrgang-competence-crit-y13-fahrzeug",
|
||||
"type": "competence.PerformanceCriteria",
|
||||
"translation_key": "e9d49552-7d18-418a-94b6-ebb4ee6bf187",
|
||||
"translation_key": "3b714984-afdb-4456-9c01-a59064724929",
|
||||
"frontend_url": "",
|
||||
"competence_id": "Y1.3"
|
||||
},
|
||||
{
|
||||
"id": 398,
|
||||
"id": 392,
|
||||
"title": "Innerhalb des Handlungsfelds \u00abFahrzeug\u00bb bin ich f\u00e4hig, die IST-Situation des Kunden mit der geeigneten Gespr\u00e4chs-/Fragetechnik zu erfassen.",
|
||||
"slug": "test-lehrgang-competence-crit-y21-fahrzeug",
|
||||
"type": "competence.PerformanceCriteria",
|
||||
"translation_key": "5f257b35-c6ca-49e4-9401-a5d02d53926d",
|
||||
"translation_key": "c2850a27-60c5-471b-9fec-ba0baf152e91",
|
||||
"frontend_url": "",
|
||||
"competence_id": "Y2.1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 385,
|
||||
"id": 378,
|
||||
"title": "Rafael Fasel wechselt sein Auto",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lc-rafael-fasel-wechselt-sein-auto",
|
||||
"type": "learnpath.LearningContent",
|
||||
"translation_key": "fda4f870-9307-414d-b07f-eea607a9afb7",
|
||||
"translation_key": "b7779d45-adf4-41fc-a4a5-e95c732b2224",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse/rafael-fasel-wechselt-sein-auto",
|
||||
"minutes": 30,
|
||||
"contents": [
|
||||
{
|
||||
|
|
@ -171,16 +299,17 @@
|
|||
"description": "In diesem Online-Training lernst du, wie du den Kundenbedarf ermittelst.",
|
||||
"url": ""
|
||||
},
|
||||
"id": "700a0f64-0892-4fa5-9e08-3bd34e99edeb"
|
||||
"id": "c79d34cb-0e7e-403d-a672-03d94cf6bdc7"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 386,
|
||||
"id": 379,
|
||||
"title": "Fachcheck Fahrzeug",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lc-fachcheck-fahrzeug",
|
||||
"type": "learnpath.LearningContent",
|
||||
"translation_key": "dce0847f-4593-4bba-bd0c-a09c71eb0344",
|
||||
"translation_key": "e395e05c-81bf-4bc6-98e8-3833bebb551c",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse/fachcheck-fahrzeug",
|
||||
"minutes": 30,
|
||||
"contents": [
|
||||
{
|
||||
|
|
@ -189,16 +318,18 @@
|
|||
"description": "Beispiel Test",
|
||||
"url": null
|
||||
},
|
||||
"id": "9f674aaa-ebf0-4a01-adcc-c0c46394fb10"
|
||||
"id": "ac4c67bc-7de9-4e5c-a35e-e13f5766d6cc"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 387,
|
||||
"id": 380,
|
||||
"title": "Reisen",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lu-reisen",
|
||||
"type": "learnpath.LearningUnit",
|
||||
"translation_key": "c3f6d33f-8dbc-4d88-9a81-3c602c4f9cc8",
|
||||
"translation_key": "d0c956cc-3c86-4e08-9990-ed4e85d03219",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse#lu-reisen",
|
||||
"evaluate_url": "/learn/test-lehrgang-lp/analyse/evaluate/reisen",
|
||||
"course_category": {
|
||||
"id": 16,
|
||||
"title": "Reisen",
|
||||
|
|
@ -206,21 +337,23 @@
|
|||
},
|
||||
"children": [
|
||||
{
|
||||
"id": 399,
|
||||
"id": 393,
|
||||
"title": "Innerhalb des Handlungsfelds \u00abReisen\u00bb bin ich f\u00e4hig, die Ziele und Pl\u00e4ne des Kunden zu ergr\u00fcnden (SOLL).",
|
||||
"slug": "test-lehrgang-competence-crit-y13-reisen",
|
||||
"type": "competence.PerformanceCriteria",
|
||||
"translation_key": "1e488b69-8a3e-4acc-9547-48c103e0d038",
|
||||
"translation_key": "1df45a12-41f2-4ff5-8580-d5a7caf5dd56",
|
||||
"frontend_url": "",
|
||||
"competence_id": "Y1.3"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 388,
|
||||
"id": 381,
|
||||
"title": "Reiseversicherung",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lc-reiseversicherung",
|
||||
"type": "learnpath.LearningContent",
|
||||
"translation_key": "ff513aae-efe1-4974-b67f-7a292b8aef86",
|
||||
"translation_key": "bad7439a-8b0c-4877-8d6c-78f292be83d4",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse/reiseversicherung",
|
||||
"minutes": 240,
|
||||
"contents": [
|
||||
{
|
||||
|
|
@ -229,16 +362,17 @@
|
|||
"description": "Beispiel \u00dcbung",
|
||||
"url": null
|
||||
},
|
||||
"id": "f35f213e-1a33-49fe-97c5-26e15161719f"
|
||||
"id": "7e1ee533-7f75-495b-a2bc-8bbd2b1311c9"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 389,
|
||||
"id": 382,
|
||||
"title": "Emma und Ayla campen durch Amerika",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lc-emma-und-ayla-campen-durch-amerika",
|
||||
"type": "learnpath.LearningContent",
|
||||
"translation_key": "a77b0f9d-9a70-47bd-8e62-7580d70a4306",
|
||||
"translation_key": "27f9d8f3-209c-4d55-94d9-2e70fbfe163b",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse/emma-und-ayla-campen-durch-amerika",
|
||||
"minutes": 120,
|
||||
"contents": [
|
||||
{
|
||||
|
|
@ -247,24 +381,41 @@
|
|||
"description": "Beispiel \u00dcbung",
|
||||
"url": "/static/media/web_based_trainings/story-06-a-01-emma-und-ayla-campen-durch-amerika-einstieg/scormcontent/index.html"
|
||||
},
|
||||
"id": "60f087ff-fa3a-4da2-820f-4fcdf449f70d"
|
||||
"id": "b08e1851-8583-4428-b1bc-402c7095130b"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 390,
|
||||
"id": 383,
|
||||
"title": "Beenden",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-ls-beenden",
|
||||
"type": "learnpath.LearningSequence",
|
||||
"translation_key": "06f1e998-b827-41cc-8129-d72d731719c1",
|
||||
"translation_key": "68be244d-0e00-4700-834c-57b4db366fc1",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse#ls-beenden",
|
||||
"icon": "it-icon-ls-end"
|
||||
},
|
||||
{
|
||||
"id": 391,
|
||||
"id": 384,
|
||||
"title": "Beenden",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lu-beenden",
|
||||
"type": "learnpath.LearningUnit",
|
||||
"translation_key": "d594db87-ad78-491b-bf1b-410adfa3a0ba",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse#lu-beenden",
|
||||
"evaluate_url": "/learn/test-lehrgang-lp/analyse/evaluate/beenden",
|
||||
"course_category": {
|
||||
"id": 14,
|
||||
"title": "Allgemein",
|
||||
"general": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 385,
|
||||
"title": "KompetenzNavi anschauen",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lc-kompetenzprofil-anschauen",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lc-kompetenznavi-anschauen",
|
||||
"type": "learnpath.LearningContent",
|
||||
"translation_key": "6cc47dc1-a74f-4cbf-afa6-23885891c82f",
|
||||
"translation_key": "8ee57ba5-e09e-4058-937a-b733ea72b969",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse/kompetenznavi-anschauen",
|
||||
"minutes": 30,
|
||||
"contents": [
|
||||
{
|
||||
|
|
@ -273,16 +424,17 @@
|
|||
"description": "Beispiel Dokument",
|
||||
"url": null
|
||||
},
|
||||
"id": "3f685055-4e3e-4ca9-93af-bac19236931d"
|
||||
"id": "3ef87e69-5e5c-415a-934c-ed47ad9fdd93"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 392,
|
||||
"id": 386,
|
||||
"title": "Circle \"Analyse\" abschliessen",
|
||||
"slug": "test-lehrgang-lp-circle-analyse-lc-circle-analyse-abschliessen",
|
||||
"type": "learnpath.LearningContent",
|
||||
"translation_key": "9b32e2cd-1368-4885-a79b-906b45ba04bc",
|
||||
"translation_key": "90d9ab63-cc0f-492f-aad1-f7d448ee5b2c",
|
||||
"frontend_url": "/learn/test-lehrgang-lp/analyse/circle-analyse-abschliessen",
|
||||
"minutes": 30,
|
||||
"contents": [
|
||||
{
|
||||
|
|
@ -291,34 +443,36 @@
|
|||
"description": "Beispiel Dokument",
|
||||
"url": null
|
||||
},
|
||||
"id": "650b7b15-b522-4df7-ac5b-6a654f12334f"
|
||||
"id": "21415232-862b-488c-9987-4f4ee369a854"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Unit-Test Circle",
|
||||
"job_situations": [
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Autoversicherung",
|
||||
"id": "c5a6b365-0a18-47d5-b6e1-6cb8b8ec7d35"
|
||||
},
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Autokauf",
|
||||
"id": "e969d2a2-b383-482c-a721-88552af086a6"
|
||||
}
|
||||
],
|
||||
"goal_description": "('In diesem Circle baust du deine Handlungskompetenzen f\u00fcr diese Themen aus:',)",
|
||||
"goals": [
|
||||
{
|
||||
"type": "goal",
|
||||
"value": "... die heutige Versicherungssituation von Privat- oder Gesch\u00e4ftskunden einzusch\u00e4tzen.",
|
||||
"id": "d9ad8aed-d7d6-42c7-b6d4-65102c8ddf10"
|
||||
"id": "d1f001fa-f7b8-41a3-90c7-632260ff7054"
|
||||
},
|
||||
{
|
||||
"type": "goal",
|
||||
"value": "... deinem Kunden seine optimale L\u00f6sung aufzuzeigen",
|
||||
"id": "2506950c-45cb-474f-acb9-45e83e9ebe1b"
|
||||
"id": "8f73bb0f-e898-4961-ab28-dd34caca2c0b"
|
||||
}
|
||||
],
|
||||
"job_situation_description": "Du triffst in diesem Circle auf die folgenden berufstypischen Handlungsfelder:",
|
||||
"job_situations": [
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Autoversicherung",
|
||||
"id": "df46930b-2911-4161-a677-75b4b156dff3"
|
||||
},
|
||||
{
|
||||
"type": "job_situation",
|
||||
"value": "Autokauf",
|
||||
"id": "17a6d252-e942-44cc-920f-015e38e727be"
|
||||
}
|
||||
],
|
||||
"experts": [
|
||||
|
|
@ -331,14 +485,14 @@
|
|||
"photo": null,
|
||||
"biography": ""
|
||||
},
|
||||
"id": "b7b0ff2e-f840-4d74-99c1-c7a5ee6dc14e"
|
||||
"id": "b0633305-5e74-43eb-93b8-ebbcfb1b17d1"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"course": {
|
||||
"id": -1,
|
||||
"title": "Test Lerngang",
|
||||
"title": "Test Lehrgang",
|
||||
"category_name": "Handlungsfeld"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,25 +5,25 @@ import requests
|
|||
|
||||
def main():
|
||||
client = requests.session()
|
||||
client.get('http://localhost:8000/')
|
||||
client.get("http://localhost:8001/")
|
||||
|
||||
client.post(
|
||||
'http://localhost:8000/api/core/login/',
|
||||
"http://localhost:8001/api/core/login/",
|
||||
json={
|
||||
'username': 'admin',
|
||||
'password': 'test',
|
||||
}
|
||||
"username": "admin",
|
||||
"password": "test",
|
||||
},
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
'http://localhost:8000/api/course/page/test-lehrgang-lp/',
|
||||
"http://localhost:8001/api/course/page/test-lehrgang-lp/",
|
||||
)
|
||||
print(response.status_code)
|
||||
print(response.json())
|
||||
|
||||
with open('learning_path_json.json', 'w') as f:
|
||||
with open("learning_path_json.json", "w") as f:
|
||||
f.write(json.dumps(response.json(), indent=4))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -4,34 +4,13 @@ import type {
|
|||
CircleGoal,
|
||||
CircleJobSituation,
|
||||
CourseCompletion,
|
||||
CourseCompletionStatus,
|
||||
CourseWagtailPage,
|
||||
LearningContent,
|
||||
LearningSequence,
|
||||
LearningUnit,
|
||||
LearningUnitPerformanceCriteria,
|
||||
WagtailCircle,
|
||||
} from "@/types";
|
||||
|
||||
function _createEmptyLearningUnit(
|
||||
parentLearningSequence: LearningSequence
|
||||
): LearningUnit {
|
||||
return {
|
||||
id: 0,
|
||||
title: "",
|
||||
slug: "",
|
||||
translation_key: "",
|
||||
type: "learnpath.LearningUnit",
|
||||
frontend_url: "",
|
||||
learningContents: [],
|
||||
minutes: 0,
|
||||
parentLearningSequence: parentLearningSequence,
|
||||
children: [],
|
||||
last: true,
|
||||
completion_status: "unknown",
|
||||
evaluate_url: "",
|
||||
};
|
||||
}
|
||||
|
||||
export function parseLearningSequences(
|
||||
circle: Circle,
|
||||
children: CircleChild[]
|
||||
|
|
@ -47,23 +26,15 @@ export function parseLearningSequences(
|
|||
if (learningSequence) {
|
||||
if (learningUnit) {
|
||||
learningUnit.last = true;
|
||||
learningSequence.learningUnits.push(learningUnit);
|
||||
}
|
||||
result.push(learningSequence);
|
||||
}
|
||||
learningSequence = Object.assign(child, { learningUnits: [] });
|
||||
|
||||
// initialize empty learning unit if there will not come a learning unit next
|
||||
learningUnit = _createEmptyLearningUnit(learningSequence);
|
||||
result.push(learningSequence);
|
||||
} else if (child.type === "learnpath.LearningUnit") {
|
||||
if (!learningSequence) {
|
||||
throw new Error("LearningUnit found before LearningSequence");
|
||||
}
|
||||
|
||||
if (learningUnit && learningUnit.learningContents.length) {
|
||||
learningSequence.learningUnits.push(learningUnit);
|
||||
}
|
||||
|
||||
learningUnit = Object.assign(child, {
|
||||
learningContents: [],
|
||||
parentLearningSequence: learningSequence,
|
||||
|
|
@ -74,9 +45,10 @@ export function parseLearningSequences(
|
|||
return c;
|
||||
}),
|
||||
});
|
||||
learningSequence.learningUnits.push(learningUnit);
|
||||
} else if (child.type === "learnpath.LearningContent") {
|
||||
if (!learningUnit) {
|
||||
throw new Error("LearningContent found before LearningUnit");
|
||||
throw new Error(`LearningContent found before LearningUnit ${child.slug}`);
|
||||
}
|
||||
previousLearningContent = learningContent;
|
||||
|
||||
|
|
@ -92,14 +64,13 @@ export function parseLearningSequences(
|
|||
}
|
||||
|
||||
learningUnit.learningContents.push(child);
|
||||
} else {
|
||||
throw new Error("Unknown CircleChild found...");
|
||||
}
|
||||
});
|
||||
|
||||
if (learningUnit && learningSequence) {
|
||||
// TypeScript does not get it here...
|
||||
if (learningUnit) {
|
||||
learningUnit.last = true;
|
||||
(learningSequence as LearningSequence).learningUnits.push(learningUnit);
|
||||
result.push(learningSequence);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Finished with LearningContent but there is no LearningSequence and LearningUnit"
|
||||
|
|
@ -121,10 +92,9 @@ export function parseLearningSequences(
|
|||
return result;
|
||||
}
|
||||
|
||||
export class Circle implements CourseWagtailPage {
|
||||
export class Circle implements WagtailCircle {
|
||||
readonly type = "learnpath.Circle";
|
||||
readonly learningSequences: LearningSequence[];
|
||||
completion_status: CourseCompletionStatus = "unknown";
|
||||
|
||||
nextCircle?: Circle;
|
||||
previousCircle?: Circle;
|
||||
|
|
@ -136,30 +106,33 @@ export class Circle implements CourseWagtailPage {
|
|||
public readonly translation_key: string,
|
||||
public readonly frontend_url: string,
|
||||
public readonly description: string,
|
||||
public children: CircleChild[],
|
||||
public goal_description: string,
|
||||
public goals: CircleGoal[],
|
||||
public job_situation_description: string,
|
||||
public job_situations: CircleJobSituation[],
|
||||
public readonly children: CircleChild[],
|
||||
public readonly goal_description: string,
|
||||
public readonly goals: CircleGoal[],
|
||||
public readonly job_situation_description: string,
|
||||
public readonly job_situations: CircleJobSituation[],
|
||||
public readonly parentLearningPath?: LearningPath
|
||||
) {
|
||||
this.learningSequences = parseLearningSequences(this, this.children);
|
||||
}
|
||||
|
||||
public static fromJson(json: any, learningPath?: LearningPath): Circle {
|
||||
public static fromJson(
|
||||
wagtailCircle: WagtailCircle,
|
||||
learningPath?: LearningPath
|
||||
): Circle {
|
||||
// TODO add error checking when the data does not conform to the schema
|
||||
return new Circle(
|
||||
json.id,
|
||||
json.slug,
|
||||
json.title,
|
||||
json.translation_key,
|
||||
json.frontend_url,
|
||||
json.description,
|
||||
json.children,
|
||||
json.goal_description,
|
||||
json.goals,
|
||||
json.job_situation_description,
|
||||
json.job_situations,
|
||||
wagtailCircle.id,
|
||||
wagtailCircle.slug,
|
||||
wagtailCircle.title,
|
||||
wagtailCircle.translation_key,
|
||||
wagtailCircle.frontend_url,
|
||||
wagtailCircle.description,
|
||||
wagtailCircle.children,
|
||||
wagtailCircle.goal_description,
|
||||
wagtailCircle.goals,
|
||||
wagtailCircle.job_situation_description,
|
||||
wagtailCircle.job_situations,
|
||||
learningPath
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import * as _ from "lodash";
|
|||
|
||||
import { Circle } from "@/services/circle";
|
||||
import type {
|
||||
Course,
|
||||
CourseCompletion,
|
||||
CourseCompletionStatus,
|
||||
CourseWagtailPage,
|
||||
LearningContent,
|
||||
LearningPathChild,
|
||||
Topic,
|
||||
WagtailLearningPath,
|
||||
} from "@/types";
|
||||
|
||||
function getLastCompleted(courseId: number, completionData: CourseCompletion[]) {
|
||||
|
|
@ -22,21 +22,23 @@ function getLastCompleted(courseId: number, completionData: CourseCompletion[])
|
|||
);
|
||||
}
|
||||
|
||||
export class LearningPath implements CourseWagtailPage {
|
||||
export class LearningPath implements WagtailLearningPath {
|
||||
readonly type = "learnpath.LearningPath";
|
||||
public topics: Topic[];
|
||||
public circles: Circle[];
|
||||
public nextLearningContent?: LearningContent;
|
||||
readonly completion_status: CourseCompletionStatus = "unknown";
|
||||
|
||||
public static fromJson(json: any, completionData: CourseCompletion[]): LearningPath {
|
||||
public static fromJson(
|
||||
json: WagtailLearningPath,
|
||||
completionData: CourseCompletion[]
|
||||
): LearningPath {
|
||||
return new LearningPath(
|
||||
json.id,
|
||||
json.slug,
|
||||
json.course.title,
|
||||
json.translation_key,
|
||||
json.frontend_url,
|
||||
json.course.id,
|
||||
json.course,
|
||||
json.children,
|
||||
completionData
|
||||
);
|
||||
|
|
@ -48,7 +50,7 @@ export class LearningPath implements CourseWagtailPage {
|
|||
public readonly title: string,
|
||||
public readonly translation_key: string,
|
||||
public readonly frontend_url: string,
|
||||
public readonly courseId: number,
|
||||
public readonly course: Course,
|
||||
public children: LearningPathChild[],
|
||||
completionData?: CourseCompletion[]
|
||||
) {
|
||||
|
|
@ -95,7 +97,7 @@ export class LearningPath implements CourseWagtailPage {
|
|||
this.nextLearningContent = undefined;
|
||||
|
||||
const lastCompletedLearningContent = getLastCompleted(
|
||||
this.courseId,
|
||||
this.course.id,
|
||||
completionData
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type {
|
|||
LearningContent,
|
||||
LearningUnit,
|
||||
LearningUnitPerformanceCriteria,
|
||||
PerformanceCriteria,
|
||||
} from "@/types";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
|
|
@ -85,17 +86,23 @@ export const useCircleStore = defineStore({
|
|||
return learningUnit;
|
||||
},
|
||||
async markCompletion(
|
||||
page: LearningContent | LearningUnitPerformanceCriteria,
|
||||
page:
|
||||
| LearningContent
|
||||
| LearningUnitPerformanceCriteria
|
||||
| PerformanceCriteria
|
||||
| undefined,
|
||||
completion_status: CourseCompletionStatus = "success"
|
||||
) {
|
||||
const completionStore = useCompletionStore();
|
||||
|
||||
try {
|
||||
if (page) {
|
||||
page.completion_status = completion_status;
|
||||
const completionData = await completionStore.markPage(page);
|
||||
if (this.circle) {
|
||||
this.circle.parseCompletionData(completionData);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return error;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { itGet } from "@/fetchHelpers";
|
||||
import { useCompletionStore } from "@/stores/completion";
|
||||
import type {
|
||||
BaseCourseWagtailPage,
|
||||
CompetencePage,
|
||||
CompetenceProfilePage,
|
||||
CourseWagtailPage,
|
||||
PerformanceCriteria,
|
||||
} from "@/types";
|
||||
import _ from "lodash";
|
||||
|
|
@ -103,9 +103,11 @@ export const useCompetenceStore = defineStore({
|
|||
|
||||
this.competenceProfilePage = competenceProfilePageData;
|
||||
|
||||
const circles = competenceProfilePageData.circles.map((c: CourseWagtailPage) => {
|
||||
const circles = competenceProfilePageData.circles.map(
|
||||
(c: BaseCourseWagtailPage) => {
|
||||
return { id: c.translation_key, name: `Circle: ${c.title}` };
|
||||
});
|
||||
}
|
||||
);
|
||||
this.availableCircles = [{ id: "all", name: "Circle: Alle" }, ...circles];
|
||||
|
||||
await this.parseCompletionData();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { itGet, itPost } from "@/fetchHelpers";
|
||||
import type { CourseCompletion, CourseWagtailPage } from "@/types";
|
||||
import type { BaseCourseWagtailPage, CourseCompletion } from "@/types";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export type CompletionStoreState = {
|
||||
|
|
@ -28,7 +28,7 @@ export const useCompletionStore = defineStore({
|
|||
this.completionData = completionData;
|
||||
return this.completionData || [];
|
||||
},
|
||||
async markPage(page: CourseWagtailPage) {
|
||||
async markPage(page: BaseCourseWagtailPage) {
|
||||
const completionData = await itPost("/api/course/completion/mark/", {
|
||||
page_key: page.translation_key,
|
||||
completion_status: page.completion_status,
|
||||
|
|
|
|||
|
|
@ -3,105 +3,132 @@ import type { Component } from "vue";
|
|||
|
||||
export type CourseCompletionStatus = "unknown" | "fail" | "success";
|
||||
|
||||
export type LearningContentType =
|
||||
| "assignment"
|
||||
| "book"
|
||||
| "document"
|
||||
| "exercise"
|
||||
| "media_library"
|
||||
| "online_training"
|
||||
| "resource"
|
||||
| "test"
|
||||
| "video"
|
||||
| "placeholder";
|
||||
|
||||
export interface LearningContentBlock {
|
||||
type: LearningContentType;
|
||||
value: {
|
||||
description: string;
|
||||
};
|
||||
id: string;
|
||||
export interface BaseCourseWagtailPage {
|
||||
readonly id: number;
|
||||
readonly title: string;
|
||||
readonly slug: string;
|
||||
readonly type: string;
|
||||
readonly translation_key: string;
|
||||
readonly frontend_url: string;
|
||||
completion_status?: CourseCompletionStatus;
|
||||
completion_status_updated_at?: string;
|
||||
}
|
||||
|
||||
export interface AssignmentBlock {
|
||||
type: "assignment";
|
||||
value: {
|
||||
description: string;
|
||||
url: string;
|
||||
};
|
||||
id: string;
|
||||
export interface CircleLight {
|
||||
readonly id: number;
|
||||
readonly title: string;
|
||||
readonly translation_key: string;
|
||||
}
|
||||
|
||||
export interface BookBlock {
|
||||
type: "book";
|
||||
value: {
|
||||
export interface BaseLearningContentBlock {
|
||||
readonly type: string;
|
||||
readonly id: string;
|
||||
readonly value: {
|
||||
description: string;
|
||||
url: string;
|
||||
text?: string;
|
||||
};
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface DocumentBlock {
|
||||
type: "document";
|
||||
value: {
|
||||
description: string;
|
||||
url: string;
|
||||
};
|
||||
id: string;
|
||||
export interface AssignmentBlock extends BaseLearningContentBlock {
|
||||
readonly type: "assignment";
|
||||
}
|
||||
|
||||
export interface ExerciseBlock {
|
||||
type: "exercise";
|
||||
value: {
|
||||
description: string;
|
||||
url: string;
|
||||
};
|
||||
id: string;
|
||||
export interface BookBlock extends BaseLearningContentBlock {
|
||||
readonly type: "book";
|
||||
}
|
||||
|
||||
export interface MediaLibraryBlock {
|
||||
type: "media_library";
|
||||
value: {
|
||||
description: string;
|
||||
url: string;
|
||||
};
|
||||
id: string;
|
||||
export interface DocumentBlock extends BaseLearningContentBlock {
|
||||
readonly type: "document";
|
||||
}
|
||||
|
||||
export interface OnlineTrainingBlock {
|
||||
type: "online_training";
|
||||
value: {
|
||||
description: string;
|
||||
url: string;
|
||||
};
|
||||
id: string;
|
||||
export interface ExerciseBlock extends BaseLearningContentBlock {
|
||||
readonly type: "exercise";
|
||||
}
|
||||
|
||||
export interface ResourceBlock {
|
||||
type: "resource";
|
||||
value: {
|
||||
description: string;
|
||||
url: string;
|
||||
};
|
||||
id: string;
|
||||
export interface MediaLibraryBlock extends BaseLearningContentBlock {
|
||||
readonly type: "media_library";
|
||||
}
|
||||
|
||||
export interface TestBlock {
|
||||
type: "test";
|
||||
value: {
|
||||
description: string;
|
||||
url: string;
|
||||
};
|
||||
id: string;
|
||||
export interface OnlineTrainingBlock extends BaseLearningContentBlock {
|
||||
readonly type: "online_training";
|
||||
}
|
||||
|
||||
export interface VideoBlock {
|
||||
type: "video";
|
||||
value: {
|
||||
description: string;
|
||||
url: string;
|
||||
};
|
||||
id: string;
|
||||
export interface ResourceBlock extends BaseLearningContentBlock {
|
||||
readonly type: "resource";
|
||||
}
|
||||
|
||||
export interface TestBlock extends BaseLearningContentBlock {
|
||||
readonly type: "test";
|
||||
}
|
||||
|
||||
export interface VideoBlock extends BaseLearningContentBlock {
|
||||
readonly type: "video";
|
||||
}
|
||||
|
||||
export interface PlaceholderBlock extends BaseLearningContentBlock {
|
||||
readonly type: "placeholder";
|
||||
}
|
||||
|
||||
export type LearningContentBlock =
|
||||
| AssignmentBlock
|
||||
| BookBlock
|
||||
| DocumentBlock
|
||||
| ExerciseBlock
|
||||
| MediaLibraryBlock
|
||||
| OnlineTrainingBlock
|
||||
| ResourceBlock
|
||||
| TestBlock
|
||||
| VideoBlock
|
||||
| PlaceholderBlock;
|
||||
|
||||
export type LearningContentType = LearningContentBlock["type"];
|
||||
|
||||
export interface LearningContent extends BaseCourseWagtailPage {
|
||||
readonly type: "learnpath.LearningContent";
|
||||
minutes: number;
|
||||
contents: LearningContentBlock[];
|
||||
parentCircle: Circle;
|
||||
parentLearningSequence?: LearningSequence;
|
||||
parentLearningUnit?: LearningUnit;
|
||||
nextLearningContent?: LearningContent;
|
||||
previousLearningContent?: LearningContent;
|
||||
}
|
||||
|
||||
export interface LearningUnitPerformanceCriteria extends BaseCourseWagtailPage {
|
||||
readonly type: "competence.PerformanceCriteria";
|
||||
readonly competence_id: string;
|
||||
parentLearningSequence?: LearningSequence;
|
||||
parentLearningUnit?: LearningUnit;
|
||||
}
|
||||
|
||||
export interface LearningUnit extends BaseCourseWagtailPage {
|
||||
readonly type: "learnpath.LearningUnit";
|
||||
readonly evaluate_url: string;
|
||||
readonly course_category: CourseCategory;
|
||||
children: LearningUnitPerformanceCriteria[];
|
||||
|
||||
// additional frontend fields
|
||||
learningContents: LearningContent[];
|
||||
minutes: number;
|
||||
parentLearningSequence?: LearningSequence;
|
||||
parentCircle?: Circle;
|
||||
last?: boolean;
|
||||
}
|
||||
|
||||
export interface LearningSequence extends BaseCourseWagtailPage {
|
||||
readonly type: "learnpath.LearningSequence";
|
||||
icon: string;
|
||||
learningUnits: LearningUnit[];
|
||||
minutes: number;
|
||||
}
|
||||
|
||||
export type CircleChild = LearningContent | LearningUnit | LearningSequence;
|
||||
|
||||
export interface WagtailLearningPath extends BaseCourseWagtailPage {
|
||||
readonly type: "learnpath.LearningPath";
|
||||
course: Course;
|
||||
children: LearningPathChild[];
|
||||
}
|
||||
|
||||
export interface CircleGoal {
|
||||
|
|
@ -116,83 +143,19 @@ export interface CircleJobSituation {
|
|||
id: string;
|
||||
}
|
||||
|
||||
export interface CourseWagtailPage {
|
||||
readonly id: number;
|
||||
readonly title: string;
|
||||
readonly slug: string;
|
||||
readonly translation_key: string;
|
||||
readonly frontend_url: string;
|
||||
completion_status: CourseCompletionStatus;
|
||||
completion_status_updated_at?: string;
|
||||
export interface WagtailCircle extends BaseCourseWagtailPage {
|
||||
readonly type: "learnpath.Circle";
|
||||
readonly description: string;
|
||||
readonly goal_description: string;
|
||||
readonly goals: CircleGoal[];
|
||||
readonly job_situation_description: string;
|
||||
readonly job_situations: CircleJobSituation[];
|
||||
readonly children: CircleChild[];
|
||||
}
|
||||
|
||||
export interface CircleLight {
|
||||
readonly id: number;
|
||||
readonly title: string;
|
||||
readonly slug: string;
|
||||
readonly translation_key: string;
|
||||
}
|
||||
|
||||
export interface LearningContent extends CourseWagtailPage {
|
||||
type: "learnpath.LearningContent";
|
||||
minutes: number;
|
||||
contents: (
|
||||
| AssignmentBlock
|
||||
| BookBlock
|
||||
| DocumentBlock
|
||||
| ExerciseBlock
|
||||
| MediaLibraryBlock
|
||||
| OnlineTrainingBlock
|
||||
| ResourceBlock
|
||||
| TestBlock
|
||||
| VideoBlock
|
||||
)[];
|
||||
parentCircle: Circle;
|
||||
parentLearningSequence?: LearningSequence;
|
||||
parentLearningUnit?: LearningUnit;
|
||||
nextLearningContent?: LearningContent;
|
||||
previousLearningContent?: LearningContent;
|
||||
}
|
||||
|
||||
export interface LearningUnitPerformanceCriteria extends CourseWagtailPage {
|
||||
type: "competence.PerformanceCriteria";
|
||||
parentLearningSequence?: LearningSequence;
|
||||
parentLearningUnit?: LearningUnit;
|
||||
}
|
||||
|
||||
export interface LearningUnit extends CourseWagtailPage {
|
||||
type: "learnpath.LearningUnit";
|
||||
learningContents: LearningContent[];
|
||||
evaluate_url: string;
|
||||
minutes: number;
|
||||
parentLearningSequence?: LearningSequence;
|
||||
parentCircle?: Circle;
|
||||
children: LearningUnitPerformanceCriteria[];
|
||||
last?: boolean;
|
||||
}
|
||||
|
||||
export interface LearningSequence extends CourseWagtailPage {
|
||||
type: "learnpath.LearningSequence";
|
||||
icon: string;
|
||||
learningUnits: LearningUnit[];
|
||||
minutes: number;
|
||||
}
|
||||
|
||||
export type CircleChild =
|
||||
| LearningContent
|
||||
| LearningUnit
|
||||
| LearningSequence
|
||||
| LearningUnitPerformanceCriteria;
|
||||
|
||||
export interface WagtailCircle extends CourseWagtailPage {
|
||||
type: "learnpath.Circle";
|
||||
children: CircleChild[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface Topic extends CourseWagtailPage {
|
||||
type: "learnpath.Topic";
|
||||
is_visible: boolean;
|
||||
export interface Topic extends BaseCourseWagtailPage {
|
||||
readonly type: "learnpath.Topic";
|
||||
readonly is_visible: boolean;
|
||||
circles: Circle[];
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +171,7 @@ export interface CourseCompletion {
|
|||
page_slug: string;
|
||||
course: number;
|
||||
completion_status: CourseCompletionStatus;
|
||||
additional_json_data: any;
|
||||
additional_json_data: unknown;
|
||||
}
|
||||
|
||||
export interface CircleDiagramData {
|
||||
|
|
@ -224,7 +187,7 @@ export interface CircleDiagramData {
|
|||
|
||||
export interface Course {
|
||||
id: number;
|
||||
name: string;
|
||||
title: string;
|
||||
category_name: string;
|
||||
}
|
||||
|
||||
|
|
@ -267,20 +230,24 @@ export interface RelativeLinkBlock {
|
|||
value: MediaLibraryContentBlockValue;
|
||||
}
|
||||
|
||||
export type MediaBlock =
|
||||
| LearnMediaBlock
|
||||
| ExternalLinkBlock
|
||||
| InternalLinkBlock
|
||||
| RelativeLinkBlock;
|
||||
|
||||
export type MediaBlockType = MediaBlock["type"];
|
||||
|
||||
export interface MediaContentCollection {
|
||||
type: "content_collection";
|
||||
value: {
|
||||
title: string;
|
||||
contents: (
|
||||
| LearnMediaBlock
|
||||
| ExternalLinkBlock
|
||||
| InternalLinkBlock
|
||||
| RelativeLinkBlock
|
||||
)[];
|
||||
description: string;
|
||||
contents: MediaBlock[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface MediaCategoryPage extends CourseWagtailPage {
|
||||
export interface MediaCategoryPage extends BaseCourseWagtailPage {
|
||||
type: "media_library.MediaCategoryPage";
|
||||
overview_icon: string;
|
||||
introduction_text: string;
|
||||
|
|
@ -290,36 +257,36 @@ export interface MediaCategoryPage extends CourseWagtailPage {
|
|||
type: "item";
|
||||
value: string;
|
||||
id: string;
|
||||
};
|
||||
}[];
|
||||
course_category: CourseCategory;
|
||||
body: MediaContentCollection[];
|
||||
}
|
||||
|
||||
export interface MediaLibraryPage extends CourseWagtailPage {
|
||||
type: "media_library.MediaLibraryPage";
|
||||
course: Course;
|
||||
children: MediaCategoryPage[];
|
||||
export interface MediaLibraryPage extends BaseCourseWagtailPage {
|
||||
readonly type: "media_library.MediaLibraryPage";
|
||||
readonly course: Course;
|
||||
readonly children: MediaCategoryPage[];
|
||||
}
|
||||
|
||||
export interface PerformanceCriteria extends CourseWagtailPage {
|
||||
type: "competence.PerformanceCriteria";
|
||||
competence_id: string;
|
||||
circle: CircleLight;
|
||||
course_category: CourseCategory;
|
||||
learning_unit: CourseWagtailPage;
|
||||
export interface PerformanceCriteria extends BaseCourseWagtailPage {
|
||||
readonly type: "competence.PerformanceCriteria";
|
||||
readonly competence_id: string;
|
||||
readonly circle: CircleLight;
|
||||
readonly course_category: CourseCategory;
|
||||
readonly learning_unit: BaseCourseWagtailPage;
|
||||
}
|
||||
|
||||
export interface CompetencePage extends CourseWagtailPage {
|
||||
type: "competence.CompetencePage";
|
||||
competence_id: string;
|
||||
children: PerformanceCriteria[];
|
||||
export interface CompetencePage extends BaseCourseWagtailPage {
|
||||
readonly type: "competence.CompetencePage";
|
||||
readonly competence_id: string;
|
||||
readonly children: PerformanceCriteria[];
|
||||
}
|
||||
|
||||
export interface CompetenceProfilePage extends CourseWagtailPage {
|
||||
type: "competence.CompetenceProfilePage";
|
||||
course: Course;
|
||||
circles: CircleLight[];
|
||||
children: CompetencePage[];
|
||||
export interface CompetenceProfilePage extends BaseCourseWagtailPage {
|
||||
readonly type: "competence.CompetenceProfilePage";
|
||||
readonly course: Course;
|
||||
readonly circles: CircleLight[];
|
||||
readonly children: CompetencePage[];
|
||||
}
|
||||
|
||||
// dropdown
|
||||
|
|
|
|||
|
|
@ -1,14 +1,32 @@
|
|||
import type { LearningContentType } from "@/types";
|
||||
import { assertUnreachable } from "@/utils/utils";
|
||||
|
||||
export const learningContentTypesToName = new Map<LearningContentType, string>([
|
||||
["assignment", "Transferauftrag"],
|
||||
["book", "Buch"],
|
||||
["document", "Dokument"],
|
||||
["exercise", "Übung"],
|
||||
["media_library", "Mediathek"],
|
||||
["online_training", "Online-Training"],
|
||||
["video", "Video"],
|
||||
["test", "Test"],
|
||||
["resource", "Seite"],
|
||||
["placeholder", "In Umsetzung"],
|
||||
]);
|
||||
export function learningContentTypeData(t: LearningContentType): {
|
||||
title: string;
|
||||
icon: string;
|
||||
} {
|
||||
switch (t) {
|
||||
case "assignment":
|
||||
return { title: "Transferauftrag", icon: "it-icon-lc-assignment" };
|
||||
case "book":
|
||||
return { title: "Buch", icon: "it-icon-lc-book" };
|
||||
case "document":
|
||||
return { title: "Dokument", icon: "it-icon-lc-document" };
|
||||
case "exercise":
|
||||
return { title: "Übung", icon: "it-icon-lc-exercise" };
|
||||
case "media_library":
|
||||
return { title: "Mediathek", icon: "it-icon-lc-media-library" };
|
||||
case "online_training":
|
||||
return { title: "Online-Training", icon: "it-icon-lc-online-training" };
|
||||
case "video":
|
||||
return { title: "Video", icon: "it-icon-lc-video" };
|
||||
case "test":
|
||||
return { title: "Test", icon: "it-icon-lc-test" };
|
||||
case "resource":
|
||||
return { title: "Ressource", icon: "it-icon-lc-resource" };
|
||||
case "placeholder":
|
||||
return { title: "In Umsetzung", icon: "it-icon-lc-document" };
|
||||
}
|
||||
|
||||
return assertUnreachable(t);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
export function assertUnreachable(x: never): never {
|
||||
throw new Error("Didn't expect to get here");
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ set -e
|
|||
echo 'prettier:check'
|
||||
(cd client && npm run prettier:check)
|
||||
|
||||
echo 'lint'
|
||||
(cd client && npm run lint)
|
||||
echo 'lint and typecheck'
|
||||
(cd client && npm run lint && npm run typecheck)
|
||||
|
||||
echo 'python ufmt check'
|
||||
ufmt check server
|
||||
|
|
|
|||
|
|
@ -185,8 +185,6 @@ gunicorn==20.1.0
|
|||
# via -r requirements.in
|
||||
h11==0.13.0
|
||||
# via uvicorn
|
||||
hiredis==2.0.0
|
||||
# via -r requirements.in
|
||||
html5lib==1.1
|
||||
# via wagtail
|
||||
httptools==0.4.0
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ Pillow # https://github.com/python-pillow/Pillow
|
|||
argon2-cffi # https://github.com/hynek/argon2_cffi
|
||||
whitenoise # https://github.com/evansd/whitenoise
|
||||
redis # https://github.com/redis/redis-py
|
||||
hiredis # https://github.com/redis/hiredis-py
|
||||
uvicorn[standard] # https://github.com/encode/uvicorn
|
||||
environs
|
||||
|
||||
|
|
|
|||
|
|
@ -106,8 +106,6 @@ gunicorn==20.1.0
|
|||
# via -r requirements.in
|
||||
h11==0.13.0
|
||||
# via uvicorn
|
||||
hiredis==2.0.0
|
||||
# via -r requirements.in
|
||||
html5lib==1.1
|
||||
# via wagtail
|
||||
httptools==0.4.0
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class CompetenceAPITestCase(APITestCase):
|
|||
self.user = User.objects.get(username="student")
|
||||
self.client.login(username="student", password="test")
|
||||
|
||||
def test_get_learnpathPage(self):
|
||||
def test_get_compentence_page(self):
|
||||
slug = "test-lehrgang-competence"
|
||||
competence_profile = CompetenceProfilePage.objects.get(slug=slug)
|
||||
response = self.client.get(f"/api/course/page/{slug}/")
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ def create_test_learning_path(user=None, skip_locales=True):
|
|||
LearningSequenceFactory(
|
||||
title="Starten", parent=circle_basis, icon="it-icon-ls-start"
|
||||
)
|
||||
LearningUnitFactory(title="Einführung", parent=circle_basis)
|
||||
LearningContentFactory(
|
||||
title="Einführung",
|
||||
parent=circle_basis,
|
||||
|
|
@ -103,6 +104,7 @@ def create_test_learning_path(user=None, skip_locales=True):
|
|||
contents=[("document", DocumentBlockFactory())],
|
||||
)
|
||||
LearningSequenceFactory(title="Beenden", parent=circle_basis, icon="it-icon-ls-end")
|
||||
LearningUnitFactory(title="Beenden", parent=circle_basis)
|
||||
LearningContentFactory(
|
||||
title="Jetzt kann es losgehen!",
|
||||
parent=circle_basis,
|
||||
|
|
@ -140,6 +142,7 @@ def create_test_learning_path(user=None, skip_locales=True):
|
|||
)
|
||||
|
||||
LearningSequenceFactory(title="Starten", parent=circle, icon="it-icon-ls-start")
|
||||
LearningUnitFactory(title="Einführung", parent=circle)
|
||||
LearningContentFactory(
|
||||
title=f'Einleitung Circle "Analyse"',
|
||||
parent=circle,
|
||||
|
|
@ -204,6 +207,7 @@ def create_test_learning_path(user=None, skip_locales=True):
|
|||
)
|
||||
|
||||
LearningSequenceFactory(title="Beenden", parent=circle, icon="it-icon-ls-end")
|
||||
LearningUnitFactory(title="Beenden", parent=circle)
|
||||
LearningContentFactory(
|
||||
title="KompetenzNavi anschauen",
|
||||
parent=circle,
|
||||
|
|
|
|||
|
|
@ -26,14 +26,6 @@ class DocumentBlock(blocks.StructBlock):
|
|||
icon = "media"
|
||||
|
||||
|
||||
class PlaceholderBlock(blocks.StructBlock):
|
||||
description = blocks.TextBlock()
|
||||
url = blocks.TextBlock()
|
||||
|
||||
class Meta:
|
||||
icon = "media"
|
||||
|
||||
|
||||
class ExerciseBlock(blocks.StructBlock):
|
||||
description = blocks.TextBlock()
|
||||
url = blocks.TextBlock()
|
||||
|
|
@ -82,3 +74,11 @@ class VideoBlock(blocks.StructBlock):
|
|||
|
||||
class Meta:
|
||||
icon = "media"
|
||||
|
||||
|
||||
class PlaceholderBlock(blocks.StructBlock):
|
||||
description = blocks.TextBlock()
|
||||
url = blocks.TextBlock()
|
||||
|
||||
class Meta:
|
||||
icon = "media"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class TestRetrieveLearingPathContents(APITestCase):
|
|||
self.user = User.objects.get(username="student")
|
||||
self.client.login(username="student", password="test")
|
||||
|
||||
def test_get_learnpathPage(self):
|
||||
def test_get_learnpath_page(self):
|
||||
slug = "test-lehrgang-lp"
|
||||
learning_path = LearningPath.objects.get(slug=slug)
|
||||
response = self.client.get(f"/api/course/page/{slug}/")
|
||||
|
|
@ -25,4 +25,4 @@ class TestRetrieveLearingPathContents(APITestCase):
|
|||
# topics and circles
|
||||
self.assertEqual(4, len(data["children"]))
|
||||
# circle "analyse" contents
|
||||
self.assertEqual(12, len(data["children"][3]["children"]))
|
||||
self.assertEqual(14, len(data["children"][3]["children"]))
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class MediaLibraryAPITestCase(APITestCase):
|
|||
self.user = User.objects.get(username="student")
|
||||
self.client.login(username="student", password="test")
|
||||
|
||||
def test_get_learnpathPage(self):
|
||||
def test_get_media_library_page(self):
|
||||
slug = "test-lehrgang-media"
|
||||
media_library = MediaLibraryPage.objects.get(slug=slug)
|
||||
response = self.client.get(f"/api/course/page/{slug}/")
|
||||
|
|
|
|||
Loading…
Reference in New Issue