Add learn media page
This commit is contained in:
parent
63a5039134
commit
a8e7b6f433
|
|
@ -13,6 +13,7 @@
|
|||
<script setup lang="ts">
|
||||
import * as log from "loglevel";
|
||||
|
||||
import AppFooter from "@/components/AppFooter.vue";
|
||||
import MainNavigationBar from "@/components/MainNavigationBar.vue";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,162 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import LinkCard from "@/components/mediaLibrary/LinkCard.vue";
|
||||
import MediaLink from "@/components/mediaLibrary/MediaLink.vue";
|
||||
import HandlungsfeldLayout from "@/pages/mediaLibrary/MLCategoryLayout.vue";
|
||||
import MLCategoryLayout from "@/pages/mediaLibrary/MLCategoryLayout.vue";
|
||||
import { useMediaLibraryStore } from "@/stores/mediaLibrary";
|
||||
import * as log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
|
||||
const field = {
|
||||
title: "Fahrzeug",
|
||||
description:
|
||||
"Das Auto ist für viele der grösste Stolz! Es birgt aber auch ein grosses Gefahrenpotenzial. Dabei geht es bei den heutigen Fahrzeugpreisen und Reparaturkosten rasch um namhafte Summen, die der Fahrzeugbesitzer und die Fahrzeugbesitzerin in einem grösseren Schadenfall oft nur schwer selbst aufbringen kann.",
|
||||
icon: "/static/icons/demo/icon-hf-fahrzeug-big.svg",
|
||||
summary: {
|
||||
text: "In diesem berufstypischem Handlungsfeld lernst du alles rund um Motorfahrzeugversicherungen, wie man sein Auto optimal schützen kann, wie du vorgehst bei einem Fahrzeugwechsel, welche Aspekte du bei einer Offerte beachten musst und wie du dem Kunden die Lösung präsentierst.",
|
||||
items: [
|
||||
"Motorfahrzeughaftpflichtversicherung",
|
||||
"Motorfahrzeugkaskoversicherung",
|
||||
"Insassenunfallversicherung",
|
||||
],
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Lernmedien",
|
||||
type: "learnmedia",
|
||||
moreLink: "",
|
||||
items: [
|
||||
{
|
||||
title: "Die Motorfahrzeughaftpflicht",
|
||||
description: "Buch «Sach- und Vermögensversicherungen» – Kapitel 16",
|
||||
iconUrl: "/static/icons/demo/icon-hf-book.png",
|
||||
linkText: "PDF anzeigen",
|
||||
link: "/static/media/documents/01a_Motorfahrzeughaftpflicht.pdf",
|
||||
openWindow: true,
|
||||
},
|
||||
{
|
||||
title: "Die Motorfahrzeughaftpflicht",
|
||||
iconUrl: "/static/icons/demo/icon-hf-book.png",
|
||||
description: "Buch «Sach- und Vermögensversicherungen» – Kapitel 16",
|
||||
linkText: "PDF anzeigen",
|
||||
link: "/static/media/documents/01a_Motorfahrzeughaftpflicht.pdf",
|
||||
openWindow: true,
|
||||
},
|
||||
{
|
||||
title: "Die Motorfahrzeughaftpflicht",
|
||||
iconUrl: "/static/icons/demo/icon-hf-book.png",
|
||||
description: "Buch «Sach- und Vermögensversicherungen» – Kapitel 16",
|
||||
linkText: "PDF anzeigen",
|
||||
link: "/static/media/documents/01a_Motorfahrzeughaftpflicht.pdf",
|
||||
openWindow: true,
|
||||
},
|
||||
{
|
||||
title: "Die Motorfahrzeughaftpflicht",
|
||||
iconUrl: "/static/icons/demo/icon-hf-book.png",
|
||||
description: "Buch «Sach- und Vermögensversicherungen» – Kapitel 16",
|
||||
linkText: "PDF anzeigen",
|
||||
link: "/static/media/documents/01a_Motorfahrzeughaftpflicht.pdf",
|
||||
openWindow: true,
|
||||
},
|
||||
{
|
||||
title: "Die Motorfahrzeughaftpflicht",
|
||||
iconUrl: "/static/icons/demo/icon-hf-book.png",
|
||||
description: "Buch «Sach- und Vermögensversicherungen» – Kapitel 16",
|
||||
linkText: "PDF anzeigen",
|
||||
link: "/static/media/documents/01a_Motorfahrzeughaftpflicht.pdf",
|
||||
openWindow: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Links",
|
||||
type: "externalLinks",
|
||||
moreLink: "",
|
||||
items: [
|
||||
{
|
||||
title: "Nationales Versicherungsbüro",
|
||||
iconUrl: "",
|
||||
description: "",
|
||||
linkText: "Link öffnen",
|
||||
link: "https://www.nbi-ngf.ch/h",
|
||||
openWindow: true,
|
||||
},
|
||||
{
|
||||
title: "Adressen der Strassenverkehrsämter",
|
||||
iconUrl: "",
|
||||
description: "",
|
||||
linkText: "Link öffnen",
|
||||
link: "https://asa.ch/strassenverkehrsaemter/adressen/",
|
||||
openWindow: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Verankerung im Lernpfad",
|
||||
type: "internalLinks",
|
||||
moreLink: "",
|
||||
items: [
|
||||
{
|
||||
title: "Circle: Einstieg – Lernsequenz: Anwenden",
|
||||
iconUrl: "",
|
||||
description: "",
|
||||
linkText: "Lerineinheit anzeigen",
|
||||
link: "http://localhost:8000/learn/versicherungsvermittlerin/versicherungsvermittlerin-circle-analyse",
|
||||
openWindow: false,
|
||||
},
|
||||
{
|
||||
title: "Circle: Einstieg – Lernsequenz: Anwenden",
|
||||
iconUrl: "",
|
||||
description: "",
|
||||
linkText: "Lerineinheit anzeigen",
|
||||
link: "http://localhost:8000/learn/versicherungsvermittlerin/versicherungsvermittlerin-circle-analyse",
|
||||
openWindow: false,
|
||||
},
|
||||
{
|
||||
title: "Circle: Einstieg – Lernsequenz: Anwenden",
|
||||
iconUrl: "",
|
||||
description: "",
|
||||
linkText: "Lerineinheit anzeigen",
|
||||
link: "http://localhost:8000/learn/versicherungsvermittlerin/versicherungsvermittlerin-circle-analyse",
|
||||
openWindow: false,
|
||||
},
|
||||
{
|
||||
title: "Circle: Einstieg – Lernsequenz: Anwenden",
|
||||
iconUrl: "",
|
||||
description: "",
|
||||
linkText: "Lerineinheit anzeigen",
|
||||
link: "http://localhost:8000/learn/versicherungsvermittlerin/versicherungsvermittlerin-circle-analyse",
|
||||
openWindow: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Querverweise",
|
||||
type: "realtiveLinks",
|
||||
moreLink: "",
|
||||
items: [
|
||||
{
|
||||
title: "Rechtsstreigkeiten",
|
||||
iconUrl: "/static/icons/demo/icon-hf-einkommenssicherung.svg",
|
||||
description:
|
||||
"Lernmedium: Verkehrsrechtsschutz – Buch «Sach- und Vermögensversicherungen/Kapitel 12.3»",
|
||||
linkText: "Handlungsfeldanzeigen",
|
||||
link: "http://localhost:8000/media/handlungsfeld",
|
||||
openWindow: false,
|
||||
},
|
||||
{
|
||||
title: "Rechtsstreigkeiten",
|
||||
iconUrl: "/static/icons/demo/icon-hf-einkommenssicherung.svg",
|
||||
description:
|
||||
"Lernmedium: Verkehrsrechtsschutz – Buch «Sach- und Vermögensversicherungen/Kapitel 12.3»",
|
||||
linkText: "Handlungsfeldanzeigen",
|
||||
link: "http://localhost:8000/media/handlungsfeld",
|
||||
openWindow: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
mediaCategorySlug: string;
|
||||
}>();
|
||||
|
|
@ -200,7 +49,7 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
|
|||
|
||||
<template>
|
||||
<Teleport v-if="mediaStore.mediaLibraryPage && mediaCategory" to="body">
|
||||
<HandlungsfeldLayout>
|
||||
<MLCategoryLayout>
|
||||
<template #header>
|
||||
<div class="flex justify-between">
|
||||
<div class="w-5/12">
|
||||
|
|
@ -208,7 +57,7 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
|
|||
<h1 class="mb-4 lg:mb-8">{{ mediaCategory.title }}</h1>
|
||||
<p>{{ mediaCategory.introduction_text }}</p>
|
||||
</div>
|
||||
<img class="w-5/12" :src="field.icon" />
|
||||
<img class="w-5/12" :src="mediaCategory.icon" />
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
|
|
@ -290,7 +139,7 @@ const hasMoreItemsForType = (itemType: string, items: object[]) => {
|
|||
</section>
|
||||
</template>
|
||||
</template>
|
||||
</HandlungsfeldLayout>
|
||||
</MLCategoryLayout>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
import { useMediaLibraryStore } from "@/stores/mediaLibrary";
|
||||
import * as log from "loglevel";
|
||||
import { ref, watch } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
log.debug("HandlungsfelderOverview created");
|
||||
|
||||
const mediaStore = useMediaLibraryStore();
|
||||
const dropdownSelected = ref(mediaStore.selectedLearningPath);
|
||||
|
||||
const categories = computed(() => {
|
||||
if (mediaStore.mediaLibraryPage) {
|
||||
return mediaStore.mediaLibraryPage.children.filter(
|
||||
(cat) => cat.course_category.general === false
|
||||
);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
watch(dropdownSelected, (newValue) =>
|
||||
mediaStore.$patch({
|
||||
selectedLearningPath: newValue,
|
||||
|
|
@ -23,11 +32,7 @@ watch(dropdownSelected, (newValue) =>
|
|||
</div>
|
||||
<div v-if="mediaStore.mediaLibraryPage">
|
||||
<ul class="grid gap-5 grid-cols-1 lg:grid-cols-4">
|
||||
<li
|
||||
v-for="cat in mediaStore.mediaLibraryPage.children"
|
||||
:key="cat.id"
|
||||
class="bg-white p-4"
|
||||
>
|
||||
<li v-for="cat in categories" :key="cat.id" class="bg-white p-4">
|
||||
<router-link :to="cat.frontend_url">
|
||||
<img class="m-auto" :src="`/static/icons/demo/${cat.overview_icon}.svg`" />
|
||||
<h3 class="text-base text-center">{{ cat.title }}</h3>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,22 @@
|
|||
import OverviewCard from "@/components/mediaLibrary/OverviewCard.vue";
|
||||
import { useMediaLibraryStore } from "@/stores/mediaLibrary";
|
||||
import * as log from "loglevel";
|
||||
import { ref, watch } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
log.debug("MediaMainView created");
|
||||
|
||||
const mediaStore = useMediaLibraryStore();
|
||||
const dropdownSelected = ref(mediaStore.selectedLearningPath);
|
||||
|
||||
const generalCategory = computed(() => {
|
||||
if (mediaStore.mediaLibraryPage) {
|
||||
return mediaStore.mediaLibraryPage.children.find(
|
||||
(cat) => cat.course_category.general
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
watch(dropdownSelected, (newValue) =>
|
||||
mediaStore.$patch({
|
||||
selectedLearningPath: newValue,
|
||||
|
|
@ -35,10 +44,10 @@ watch(dropdownSelected, (newValue) =>
|
|||
>
|
||||
</OverviewCard>
|
||||
<OverviewCard
|
||||
v-if="mediaStore.mediaLibraryPage"
|
||||
v-if="mediaStore.mediaLibraryPage && generalCategory"
|
||||
title="Lernmedien"
|
||||
call2-action="Anschauen"
|
||||
:link="`/media/${mediaStore.mediaLibraryPage.slug}/lernmedien`"
|
||||
:link="`${generalCategory.frontend_url}/media`"
|
||||
description="Finde eine vollständige Liste der Bücher und anderen Medien, auf die im Kurs verwiesen wird."
|
||||
icon="lernmedien-overview"
|
||||
class="mb-6"
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import MediaLink from "@/components/mediaLibrary/MediaLink.vue";
|
||||
import HandlungsfeldLayout from "@/pages/mediaLibrary/MLCategoryLayout.vue";
|
||||
|
||||
const data = {
|
||||
title: "Fahrzeug: Lernmedien",
|
||||
items: [
|
||||
{
|
||||
title: "Die Motorfahrzeughaftpflicht",
|
||||
description: "Buch «Sach- und Vermögensversicherungen» – Kapitel 16",
|
||||
iconUrl: "/static/icons/demo/icon-hf-book.png",
|
||||
linkText: "PDF anzeigen",
|
||||
link: "/static/media/documents/01a_Motorfahrzeughaftpflicht.pdf",
|
||||
openWindow: true,
|
||||
},
|
||||
{
|
||||
title: "Die Motorfahrzeughaftpflicht",
|
||||
iconUrl: "/static/icons/demo/icon-hf-book.png",
|
||||
description: "Buch «Sach- und Vermögensversicherungen» – Kapitel 16",
|
||||
linkText: "PDF anzeigen",
|
||||
link: "/static/media/documents/01a_Motorfahrzeughaftpflicht.pdf",
|
||||
openWindow: true,
|
||||
},
|
||||
{
|
||||
title: "Die Motorfahrzeughaftpflicht",
|
||||
iconUrl: "/static/icons/demo/icon-hf-book.png",
|
||||
description: "Buch «Sach- und Vermögensversicherungen» – Kapitel 16",
|
||||
linkText: "PDF anzeigen",
|
||||
link: "/static/media/documents/01a_Motorfahrzeughaftpflicht.pdf",
|
||||
openWindow: true,
|
||||
},
|
||||
{
|
||||
title: "Die Motorfahrzeughaftpflicht",
|
||||
iconUrl: "/static/icons/demo/icon-hf-book.png",
|
||||
description: "Buch «Sach- und Vermögensversicherungen» – Kapitel 16",
|
||||
linkText: "PDF anzeigen",
|
||||
link: "/static/media/documents/01a_Motorfahrzeughaftpflicht.pdf",
|
||||
openWindow: true,
|
||||
},
|
||||
{
|
||||
title: "Die Motorfahrzeughaftpflicht",
|
||||
iconUrl: "/static/icons/demo/icon-hf-book.png",
|
||||
description: "Buch «Sach- und Vermögensversicherungen» – Kapitel 16",
|
||||
linkText: "PDF anzeigen",
|
||||
link: "/static/media/documents/01a_Motorfahrzeughaftpflicht.pdf",
|
||||
openWindow: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<HandlungsfeldLayout>
|
||||
<template #header>
|
||||
<h1 class="mb-4">{{ data.title }}</h1>
|
||||
</template>
|
||||
<template #body>
|
||||
<section class="mb-20">
|
||||
<ul class="border-t">
|
||||
<li
|
||||
v-for="item in data.items"
|
||||
:key="item.link"
|
||||
class="flex items-center justify-between border-b py-4"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div v-if="item.iconUrl">
|
||||
<img class="mr-6 max-h-[70px]" :src="item.iconUrl" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-bold">{{ item.title }}</h4>
|
||||
<p v-if="item.description" class="mb-2">{{ item.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<media-link :to="item.link" :blank="item.openWindow" class="link"
|
||||
>{{ item.linkText }}
|
||||
</media-link>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
</HandlungsfeldLayout>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.it-icon-hf {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.it-icon-hf > * {
|
||||
@apply m-auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<script setup lang="ts">
|
||||
import MediaLink from "@/components/mediaLibrary/MediaLink.vue";
|
||||
import MLCategoryLayout from "@/pages/mediaLibrary/MLCategoryLayout.vue";
|
||||
import { useMediaLibraryStore } from "@/stores/mediaLibrary";
|
||||
import * as log from "loglevel";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
mediaLibraryPageSlug: string;
|
||||
mediaCategorySlug: string;
|
||||
}>();
|
||||
|
||||
log.debug("MLMediaListPage created", props.mediaCategorySlug);
|
||||
|
||||
const mediaStore = useMediaLibraryStore();
|
||||
|
||||
const mediaCategory = computed(() => {
|
||||
return mediaStore.mediaLibraryPage?.children.find((category) =>
|
||||
category.slug.endsWith(props.mediaCategorySlug)
|
||||
);
|
||||
});
|
||||
|
||||
const mediaList = computed(() => {
|
||||
if (mediaCategory.value) {
|
||||
const learnMediaCollection = mediaCategory.value.body.find((contentCollection) => {
|
||||
if (contentCollection.value.contents.length) {
|
||||
return contentCollection.value.contents[0].type === "learn_media";
|
||||
}
|
||||
});
|
||||
console.log(learnMediaCollection);
|
||||
return learnMediaCollection?.value;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport v-if="mediaList" to="body">
|
||||
<MLCategoryLayout>
|
||||
<template #header>
|
||||
<h1 class="mb-4">{{ mediaList.title }}</h1>
|
||||
</template>
|
||||
<template #body>
|
||||
<section class="mb-20">
|
||||
<ul class="border-t">
|
||||
<li
|
||||
v-for="item in mediaList.contents"
|
||||
:key="item.id"
|
||||
class="flex items-center justify-between border-b py-4"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div v-if="item.value.icon_url">
|
||||
<img class="mr-6 max-h-[70px]" :src="item.value.icon_url" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-bold">{{ item.value.title }}</h4>
|
||||
<p v-if="item.value.description" class="mb-2">
|
||||
{{ item.value.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<media-link
|
||||
:to="item.value.url"
|
||||
:blank="item.value.open_window"
|
||||
class="link"
|
||||
>{{ item.value.link_display_text }}
|
||||
</media-link>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
</MLCategoryLayout>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.it-icon-hf {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.it-icon-hf > * {
|
||||
@apply m-auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { redirectToLoginIfRequired, updateLoggedIn } from "@/router/guards";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import CockpitView from "@/pages/CockpitView.vue";
|
||||
import LoginView from "@/pages/LoginView.vue";
|
||||
import { redirectToLoginIfRequired, updateLoggedIn } from "@/router/guards";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
|
||||
const router = createRouter({
|
||||
|
|
@ -35,7 +35,8 @@ const router = createRouter({
|
|||
},
|
||||
{
|
||||
path: "category/:mediaCategorySlug/media",
|
||||
component: () => import("@/pages/mediaLibrary/MLMediaList.vue"),
|
||||
props: true,
|
||||
component: () => import("@/pages/mediaLibrary/MLMediaListPage.vue"),
|
||||
},
|
||||
{
|
||||
path: "category/:mediaCategorySlug",
|
||||
|
|
@ -44,8 +45,7 @@ const router = createRouter({
|
|||
},
|
||||
{
|
||||
path: "category",
|
||||
component: () =>
|
||||
import("@/pages/mediaLibrary/MLCategoryIndexPage.vue"),
|
||||
component: () => import("@/pages/mediaLibrary/MLCategoryIndexPage.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -222,28 +222,49 @@ export interface CourseCategory {
|
|||
general: boolean;
|
||||
}
|
||||
|
||||
export interface MediaDocument {
|
||||
type: "Documents";
|
||||
value: number;
|
||||
export type MediaLibraryContentBlockValue = {
|
||||
title: string;
|
||||
description: string;
|
||||
icon_url: string;
|
||||
link_display_text: string;
|
||||
url: string;
|
||||
open_window: boolean;
|
||||
};
|
||||
|
||||
export interface LearnMediaBlock {
|
||||
type: "learn_media";
|
||||
id: string;
|
||||
value: MediaLibraryContentBlockValue;
|
||||
}
|
||||
|
||||
export interface MediaLink {
|
||||
type: "Links";
|
||||
export interface ExternalLinkBlock {
|
||||
type: "external_link";
|
||||
id: string;
|
||||
value: {
|
||||
title: string;
|
||||
description: string;
|
||||
link_display_text: string;
|
||||
url: string;
|
||||
};
|
||||
value: MediaLibraryContentBlockValue;
|
||||
}
|
||||
|
||||
export interface InternalLinkBlock {
|
||||
type: "internal_link";
|
||||
id: string;
|
||||
value: MediaLibraryContentBlockValue;
|
||||
}
|
||||
|
||||
export interface RelativeLinkBlock {
|
||||
type: "relative_link";
|
||||
id: string;
|
||||
value: MediaLibraryContentBlockValue;
|
||||
}
|
||||
|
||||
export interface MediaContentCollection {
|
||||
type: "content_collection";
|
||||
value: {
|
||||
title: string;
|
||||
contents: (MediaDocument | MediaLink)[];
|
||||
contents: (
|
||||
| LearnMediaBlock
|
||||
| ExternalLinkBlock
|
||||
| InternalLinkBlock
|
||||
| RelativeLinkBlock
|
||||
)[];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ logger = structlog.get_logger(__name__)
|
|||
|
||||
|
||||
@api_view(["GET"])
|
||||
# TODO readd
|
||||
# @cache_page(60 * 60 * 8, cache="api_page_cache")
|
||||
def page_api_view(request, slug):
|
||||
try:
|
||||
page = Page.objects.get(slug=slug, locale__language_code="de-CH")
|
||||
|
|
|
|||
Loading…
Reference in New Issue