feat(backend): SingleTheme-pagina geïmplementeerd
This commit is contained in:
parent
a33ec6c452
commit
34f980d690
7 changed files with 137 additions and 49 deletions
54
frontend/src/components/LearningPathsGrid.vue
Normal file
54
frontend/src/components/LearningPathsGrid.vue
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import {convertBase64ToImageSrc} from "@/utils/base64ToImage.ts";
|
||||||
|
import type {LearningPath} from "@/data-objects/learning-path.ts";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps<{learningPaths: LearningPath[]}>();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="results-grid" v-if="props.learningPaths.length > 0">
|
||||||
|
<v-card
|
||||||
|
class="learning-path-card"
|
||||||
|
link
|
||||||
|
:to="`/learningPath/${learningPath.hruid}/${learningPath.language}/${learningPath.startNode.learningobjectHruid}`"
|
||||||
|
v-for="learningPath in props.learningPaths"
|
||||||
|
>
|
||||||
|
<v-img
|
||||||
|
height="300px"
|
||||||
|
:src="convertBase64ToImageSrc(learningPath.image)"
|
||||||
|
cover
|
||||||
|
v-if="learningPath.image"
|
||||||
|
></v-img>
|
||||||
|
<v-card-title>{{ learningPath.title }}</v-card-title>
|
||||||
|
<v-card-subtitle>
|
||||||
|
<v-icon icon="mdi-human-male-boy"></v-icon>
|
||||||
|
<span>{{ learningPath.targetAges.min }} - {{ learningPath.targetAges.max }} {{ t('yearsAge') }}</span>
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>{{ learningPath.description }}</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
<div content="empty-state-container" v-else>
|
||||||
|
<v-empty-state
|
||||||
|
icon="mdi-emoticon-sad-outline"
|
||||||
|
:title="t('noLearningPathsFound')"
|
||||||
|
:text="t('noLearningPathsFoundDescription')"
|
||||||
|
></v-empty-state>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.learning-path-card {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
.results-grid {
|
||||||
|
margin: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -21,4 +21,8 @@ export class LearningPathController extends BaseController {
|
||||||
});
|
});
|
||||||
return LearningPath.fromDTO(single(dtos));
|
return LearningPath.fromDTO(single(dtos));
|
||||||
}
|
}
|
||||||
|
async getAllByTheme(theme: string) {
|
||||||
|
let dtos = await this.get<LearningPathDTO[]>("/", {theme});
|
||||||
|
return dtos.map(dto => LearningPath.fromDTO(dto));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import type {Language} from "@/data-objects/language.ts";
|
import type {Language} from "@/data-objects/language.ts";
|
||||||
import type {LearningObject} from "@/data-objects/learning-object.ts";
|
|
||||||
import {getLearningObjectMetadata} from "@/services/learning-content/learning-object-service.ts";
|
|
||||||
|
|
||||||
export interface LearningPathDTO {
|
export interface LearningPathDTO {
|
||||||
language: string;
|
language: string;
|
||||||
|
@ -54,10 +52,6 @@ export class LearningPathNode {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
get learningObject(): Promise<LearningObject> {
|
|
||||||
return getLearningObjectMetadata(this.learningobjectHruid, this.language, this.version);
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromDTOAndOtherNodes(dto: LearningPathNodeDTO, otherNodes: LearningPathNodeDTO[]): LearningPathNode {
|
static fromDTOAndOtherNodes(dto: LearningPathNodeDTO, otherNodes: LearningPathNodeDTO[]): LearningPathNode {
|
||||||
return new LearningPathNode(
|
return new LearningPathNode(
|
||||||
dto.learningobject_hruid,
|
dto.learningobject_hruid,
|
||||||
|
|
|
@ -22,6 +22,18 @@ export function useGetLearningPathQuery(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useGetAllLearningPathsByThemeQuery(
|
||||||
|
theme: MaybeRefOrGetter<string>
|
||||||
|
): UseQueryReturnType<LearningPath[], Error> {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [LEARNING_PATH_KEY, "getAllByTheme", theme],
|
||||||
|
queryFn: () => {
|
||||||
|
return learningPathController.getAllByTheme(toValue(theme))
|
||||||
|
},
|
||||||
|
enabled: () => Boolean(toValue(theme)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function useSearchLearningPathQuery(
|
export function useSearchLearningPathQuery(
|
||||||
query: MaybeRefOrGetter<string>
|
query: MaybeRefOrGetter<string>
|
||||||
): UseQueryReturnType<LearningPath[], Error> {
|
): UseQueryReturnType<LearningPath[], Error> {
|
||||||
|
|
|
@ -65,9 +65,10 @@ const router = createRouter({
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "/theme/:id",
|
path: "/theme/:theme",
|
||||||
name: "Theme",
|
name: "Theme",
|
||||||
component: SingleTheme,
|
component: SingleTheme,
|
||||||
|
props: true,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,67 @@
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import type {LearningPath} from "@/data-objects/learning-path.ts";
|
||||||
|
import LearningPathsGrid from "@/components/LearningPathsGrid.vue";
|
||||||
|
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
||||||
|
import {useGetAllLearningPathsByThemeQuery} from "@/queries/learning-paths.ts";
|
||||||
|
import {computed, ref} from "vue";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
import {useThemeQuery} from "@/queries/themes.ts";
|
||||||
|
|
||||||
|
const props = defineProps<{theme: string}>();
|
||||||
|
|
||||||
|
const { locale } = useI18n();
|
||||||
|
const language = computed(() => locale.value);
|
||||||
|
|
||||||
|
const themeQueryResult = useThemeQuery(language);
|
||||||
|
|
||||||
|
const currentThemeInfo = computed(() =>
|
||||||
|
themeQueryResult.isSuccess.value ? themeQueryResult.data.value.filter(it => it.key === props.theme)[0] : undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const learningPathsForThemeQueryResult = useGetAllLearningPathsByThemeQuery(() => props.theme);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const searchFilter = ref("");
|
||||||
|
|
||||||
|
function filterLearningPaths(learningPaths: LearningPath[]) {
|
||||||
|
return learningPaths.filter(it =>
|
||||||
|
it.title.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||||
|
|| it.description.toLowerCase().includes(searchFilter.value.toLowerCase)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main></main>
|
<div class="container">
|
||||||
|
<using-query-result :query-result="themeQueryResult">
|
||||||
|
<h1>{{ currentThemeInfo.title }}</h1>
|
||||||
|
<p>{{ currentThemeInfo.description }}</p>
|
||||||
|
<div class="search-field-container">
|
||||||
|
<v-text-field
|
||||||
|
class="search-field"
|
||||||
|
:label="t('search')"
|
||||||
|
append-inner-icon="mdi-magnify"
|
||||||
|
v-model="searchFilter"
|
||||||
|
></v-text-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<using-query-result :query-result="learningPathsForThemeQueryResult" v-slot="{ data }: {data: LearningPath[]}">
|
||||||
|
<learning-paths-grid :learning-paths="filterLearningPaths(data)"></learning-paths-grid>
|
||||||
|
</using-query-result>
|
||||||
|
</using-query-result>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.search-field-container {
|
||||||
|
display: block;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
.search-field {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
import {useRoute, useRouter} from "vue-router";
|
import {useRoute, useRouter} from "vue-router";
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
import {useI18n} from "vue-i18n";
|
import {useI18n} from "vue-i18n";
|
||||||
import {convertBase64ToImageSrc} from "@/utils/base64ToImage.ts";
|
|
||||||
import LearningPathSearchField from "@/components/LearningPathSearchField.vue";
|
import LearningPathSearchField from "@/components/LearningPathSearchField.vue";
|
||||||
import {useSearchLearningPathQuery} from "@/queries/learning-paths.ts";
|
import {useSearchLearningPathQuery} from "@/queries/learning-paths.ts";
|
||||||
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
||||||
|
import LearningPathsGrid from "@/components/LearningPathsGrid.vue";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -23,34 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<using-query-result :query-result="searchQueryResults" v-slot="{ data }: {data: LearningPath[]}">
|
<using-query-result :query-result="searchQueryResults" v-slot="{ data }: {data: LearningPath[]}">
|
||||||
<div class="results-grid" v-if="data.length > 0">
|
<learning-paths-grid :learning-paths="data"></learning-paths-grid>
|
||||||
<v-card
|
|
||||||
class="learning-path-card"
|
|
||||||
link
|
|
||||||
:to="`${learningPath.hruid}/${learningPath.language}/${learningPath.startNode.learningobjectHruid}`"
|
|
||||||
v-for="learningPath in data"
|
|
||||||
>
|
|
||||||
<v-img
|
|
||||||
height="300px"
|
|
||||||
:src="convertBase64ToImageSrc(learningPath.image)"
|
|
||||||
cover
|
|
||||||
v-if="learningPath.image"
|
|
||||||
></v-img>
|
|
||||||
<v-card-title>{{ learningPath.title }}</v-card-title>
|
|
||||||
<v-card-subtitle>
|
|
||||||
<v-icon icon="mdi-human-male-boy"></v-icon>
|
|
||||||
<span>{{ learningPath.targetAges.min }} - {{ learningPath.targetAges.max }} {{ t('yearsAge') }}</span>
|
|
||||||
</v-card-subtitle>
|
|
||||||
<v-card-text>{{ learningPath.description }}</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
<div content="empty-state-container" v-else>
|
|
||||||
<v-empty-state
|
|
||||||
icon="mdi-emoticon-sad-outline"
|
|
||||||
:title="t('noLearningPathsFound')"
|
|
||||||
:text="t('noLearningPathsFoundDescription')"
|
|
||||||
></v-empty-state>
|
|
||||||
</div>
|
|
||||||
</using-query-result>
|
</using-query-result>
|
||||||
<div content="empty-state-container">
|
<div content="empty-state-container">
|
||||||
<v-empty-state
|
<v-empty-state
|
||||||
|
@ -67,17 +40,7 @@
|
||||||
display: block;
|
display: block;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
.results-grid {
|
|
||||||
margin: 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 20px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.search-field {
|
.search-field {
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
.learning-path-card {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue