Add Category Pills to Module
This commit is contained in:
parent
475afd03ed
commit
6d312da0ae
|
|
@ -4,12 +4,23 @@
|
|||
class="module"
|
||||
v-if="module.id"
|
||||
>
|
||||
<h2
|
||||
class="module__meta-title"
|
||||
id="meta-title"
|
||||
>
|
||||
{{ module.metaTitle }}
|
||||
</h2>
|
||||
<div class="module__header">
|
||||
<div>
|
||||
<h2
|
||||
class="module__meta-title"
|
||||
id="meta-title"
|
||||
>
|
||||
{{ module.metaTitle }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<pill :text="module.category?.name"></pill>
|
||||
<pill :text="module.categoryType?.name"></pill>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h1
|
||||
class="module__title"
|
||||
data-cy="module-title"
|
||||
|
|
@ -85,116 +96,127 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue';
|
||||
import Chapter from '@/components/Chapter.vue';
|
||||
import BookmarkActions from '@/components/notes/BookmarkActions.vue';
|
||||
import ObjectiveGroups from '@/components/objective-groups/ObjectiveGroups.vue';
|
||||
import Chapter from '@/components/Chapter.vue';
|
||||
import BookmarkActions from '@/components/notes/BookmarkActions.vue';
|
||||
import Pill from "@/components/ui/Pill.vue";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
module: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
export default {
|
||||
props: {
|
||||
module: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
BookmarkActions,
|
||||
ObjectiveGroups,
|
||||
Chapter,
|
||||
},
|
||||
components: {
|
||||
Pill,
|
||||
BookmarkActions,
|
||||
ObjectiveGroups,
|
||||
Chapter,
|
||||
},
|
||||
|
||||
computed: {
|
||||
languageCommunicationObjectiveGroups() {
|
||||
return this.module.objectiveGroups
|
||||
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'language_communication')
|
||||
: [];
|
||||
computed: {
|
||||
languageCommunicationObjectiveGroups() {
|
||||
return this.module.objectiveGroups
|
||||
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'language_communication')
|
||||
: [];
|
||||
},
|
||||
societyObjectiveGroups() {
|
||||
return this.module.objectiveGroups
|
||||
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'society')
|
||||
: [];
|
||||
},
|
||||
interdisciplinaryObjectiveGroups() {
|
||||
return this.module.objectiveGroups
|
||||
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'interdisciplinary')
|
||||
: [];
|
||||
},
|
||||
note() {
|
||||
if (!(this.module && this.module.bookmark)) {
|
||||
return;
|
||||
}
|
||||
return this.module.bookmark.note;
|
||||
},
|
||||
},
|
||||
societyObjectiveGroups() {
|
||||
return this.module.objectiveGroups
|
||||
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'society')
|
||||
: [];
|
||||
},
|
||||
interdisciplinaryObjectiveGroups() {
|
||||
return this.module.objectiveGroups
|
||||
? this.module.objectiveGroups.filter((group) => group.title.toLowerCase() === 'interdisciplinary')
|
||||
: [];
|
||||
},
|
||||
note() {
|
||||
if (!(this.module && this.module.bookmark)) {
|
||||
return;
|
||||
}
|
||||
return this.module.bookmark.note;
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import 'styles/helpers';
|
||||
@import 'styles/helpers';
|
||||
|
||||
.module {
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
max-width: 100vw;
|
||||
.module {
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
max-width: 100vw;
|
||||
|
||||
padding: $large-spacing 0;
|
||||
@include desktop {
|
||||
width: 800px;
|
||||
padding: $large-spacing 15px;
|
||||
}
|
||||
flex-direction: column;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: $large-spacing 0;
|
||||
@include desktop {
|
||||
width: 800px;
|
||||
padding: $large-spacing 15px;
|
||||
}
|
||||
flex-direction: column;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__hero {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
&__hero-image {
|
||||
max-width: 100%;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
&__hero-source {
|
||||
@include tiny-text;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
&__meta-title {
|
||||
@include meta-title;
|
||||
}
|
||||
|
||||
&__intro-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__intro {
|
||||
> :deep(p) {
|
||||
margin-bottom: $large-spacing;
|
||||
@include lead-paragraph;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&__hero {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
> :deep(ul) {
|
||||
@include list-parent;
|
||||
&__hero-image {
|
||||
max-width: 100%;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
> li {
|
||||
@include list-child;
|
||||
&__hero-source {
|
||||
@include tiny-text;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&__meta-title {
|
||||
@include meta-title;
|
||||
margin-right: 20px;
|
||||
|
||||
}
|
||||
|
||||
&__intro-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__intro {
|
||||
> :deep(p) {
|
||||
margin-bottom: $large-spacing;
|
||||
@include lead-paragraph;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> :deep(ul) {
|
||||
@include list-parent;
|
||||
|
||||
> li {
|
||||
@include list-child;
|
||||
@include lead-paragraph;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__bookmark-actions {
|
||||
margin-top: 3px;
|
||||
}
|
||||
&__bookmark-actions {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
&__objective-groups {
|
||||
margin-bottom: 2 * $large-spacing;
|
||||
&__objective-groups {
|
||||
margin-bottom: 2 * $large-spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@
|
|||
|
||||
function filterModules() {
|
||||
let filteredModules = props.modules;
|
||||
|
||||
|
||||
if (selectedCategory.value === null) {
|
||||
return props.modules;
|
||||
}
|
||||
|
|
@ -108,14 +110,14 @@
|
|||
// filter by Lehrjahr
|
||||
if (selectedCategory.value.name !== '---') {
|
||||
filteredModules = filteredModules.filter((module) => {
|
||||
return module.category.id == selectedCategory.value.id;
|
||||
return module.category?.id == selectedCategory.value.id;
|
||||
});
|
||||
}
|
||||
|
||||
//filter by Lernfeld
|
||||
if (selectedLernfeld.value.name !== '---') {
|
||||
filteredModules = filteredModules.filter((module) => {
|
||||
return module.categoryType.id == selectedLernfeld.value.id;
|
||||
return module.categoryType?.id == selectedLernfeld.value.id;
|
||||
});
|
||||
}
|
||||
updateLastModuleCategory(selectedCategory.value);
|
||||
|
|
|
|||
|
|
@ -17,89 +17,88 @@
|
|||
<p class="module-teaser__description">
|
||||
{{ teaser }}
|
||||
</p>
|
||||
<span :value="attribute" v-for="attribute in [category.name, categoryType.name]">
|
||||
<div class="module-teaser__module-category" v-if="attribute">{{attribute}}</div>
|
||||
</span>
|
||||
<div class="module-teaser__pills">
|
||||
<pill :text="category?.name"></pill>
|
||||
<pill :text="categoryType?.name"></pill>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['metaTitle', 'title', 'teaser', 'id', 'slug', 'heroImage', 'category', 'categoryType'],
|
||||
import Pill from "@/components/ui/Pill.vue";
|
||||
|
||||
computed: {
|
||||
moduleLink() {
|
||||
if (this.slug) {
|
||||
return {
|
||||
name: 'module',
|
||||
params: {
|
||||
slug: this.slug,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
export default {
|
||||
components: {Pill},
|
||||
props: ['metaTitle', 'title', 'teaser', 'id', 'slug', 'heroImage', 'category', 'categoryType'],
|
||||
|
||||
|
||||
computed: {
|
||||
moduleLink() {
|
||||
if (this.slug) {
|
||||
return {
|
||||
name: 'module',
|
||||
params: {
|
||||
slug: this.slug,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import 'styles/helpers';
|
||||
@import 'styles/helpers';
|
||||
|
||||
.module-teaser {
|
||||
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.12);
|
||||
border: 1px solid #e2e2e2;
|
||||
height: 390px;
|
||||
max-width: 380px;
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
&--small {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
&__image {
|
||||
.module-teaser {
|
||||
box-shadow: 0 3px 9px 0 rgba(0, 0, 0, 0.12);
|
||||
border: 1px solid #e2e2e2;
|
||||
height: 390px;
|
||||
max-width: 380px;
|
||||
width: 100%;
|
||||
max-height: 150px;
|
||||
height: 150px;
|
||||
background-position: center;
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
&__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
&__meta-title {
|
||||
color: $color-silver-dark;
|
||||
margin-bottom: $large-spacing;
|
||||
@include regular-text;
|
||||
}
|
||||
|
||||
&__title {
|
||||
@include heading-3;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
line-height: $default-line-height;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
&__module-category {
|
||||
background-color: rgba(129, 129, 129, 0.99); /* Replace with your desired background color */
|
||||
color: #fff; /* Replace with your desired text color */
|
||||
padding: 10px 20px;
|
||||
border-radius: 40px; /* A high value to make it look like a pill */
|
||||
display: inline-block; /* Ensures the pill takes only the necessary width */
|
||||
margin-right: 10px;
|
||||
margin-top: 20px;
|
||||
&--small {
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
max-height: 150px;
|
||||
height: 150px;
|
||||
background-position: center;
|
||||
background-size: 100% auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
&__meta-title {
|
||||
color: $color-silver-dark;
|
||||
margin-bottom: $large-spacing;
|
||||
@include regular-text;
|
||||
}
|
||||
|
||||
&__title {
|
||||
@include heading-3;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
line-height: $default-line-height;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
&__pills {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import Avatar from '@/components/profile/Avatar.vue';
|
|||
import { defineAsyncComponent } from 'vue';
|
||||
const TrashIcon = defineAsyncComponent(() => import('@/components/icons/TrashIcon.vue'));
|
||||
|
||||
|
||||
// TODO: Kann das mit me.ts zusammengeführt werden?
|
||||
export default {
|
||||
components: {
|
||||
AvatarUploadForm,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<div class="pill" v-if="props.text">{{ props.text }}</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
text: [];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import 'styles/helpers';
|
||||
|
||||
.pill {
|
||||
background-color: white; /* Replace with your desired background color */
|
||||
color: #333333; /* Replace with your desired text color */
|
||||
padding: 10px 20px;
|
||||
border-radius: 30px;
|
||||
border: 1px solid silver;
|
||||
display: inline-block; /* Ensures the pill takes only the necessary width */
|
||||
margin-right: 10px;
|
||||
margin-top: 2px;
|
||||
@include small-text;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -14,6 +14,7 @@ export interface Me {
|
|||
team: any;
|
||||
lastTopic: any;
|
||||
readOnly: boolean;
|
||||
lastModuleCategory: any;
|
||||
}
|
||||
|
||||
export interface MeQuery {
|
||||
|
|
@ -30,6 +31,8 @@ export interface Location {
|
|||
|
||||
type RouteLocation = Location | string;
|
||||
|
||||
|
||||
// TODO: ME_QUERY existiert an einem weiteren Ort. Dieser sollte entfernt werden.
|
||||
const defaultMe: MeQuery = {
|
||||
me: {
|
||||
selectedClass: {
|
||||
|
|
@ -42,6 +45,7 @@ const defaultMe: MeQuery = {
|
|||
team: null,
|
||||
readOnly: false,
|
||||
lastTopic: undefined,
|
||||
lastModuleCategory: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -76,6 +80,10 @@ const getCurrentClassName = (me: Me) => {
|
|||
return currentClass ? currentClass.name : me.schoolClasses.length ? me.schoolClasses[0].name : '';
|
||||
};
|
||||
|
||||
const getLastModuleCategory = (me: Me) => {
|
||||
return me.lastModuleCategory;
|
||||
}
|
||||
|
||||
const getMe = () => {
|
||||
const { result } = useQuery(ME_QUERY);
|
||||
|
||||
|
|
@ -126,6 +134,10 @@ const meMixin = {
|
|||
// @ts-ignore
|
||||
return getCurrentClassName(this.$data.me);
|
||||
},
|
||||
lastModuleCategory(): any {
|
||||
// @ts-ignore
|
||||
return getLastModuleCategory(this.$data.me);
|
||||
}
|
||||
},
|
||||
|
||||
apollo: {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from django.utils import timezone
|
|||
from graphene import relay
|
||||
|
||||
from api.utils import get_object
|
||||
from books.models import Module, RecentModule
|
||||
from books.models import Module, RecentModule, ModuleCategory
|
||||
from books.schema.nodes import ModuleNode
|
||||
from users.models import SchoolClass, User
|
||||
from users.schema import PrivateUserNode
|
||||
|
|
@ -115,6 +115,8 @@ class UpdateLastModuleCategory(relay.ClientIDMutation):
|
|||
def mutate_and_get_payload(cls, root, info, **args):
|
||||
user = info.context.user
|
||||
id = args.get('id')
|
||||
module_category = get_object(ModuleCategory, id)
|
||||
|
||||
User.objects.filter(pk=user.id).update(last_module_category_id=id)
|
||||
|
||||
User.objects.filter(pk=user.id).update(last_module_category_id=module_category.id)
|
||||
return cls(user=user)
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ class ModuleNode(DjangoObjectType):
|
|||
|
||||
@staticmethod
|
||||
def resolve_objective_groups(parent, info, **kwargs):
|
||||
return parent.objective_groups.all().prefetch_related("hidden_for") @ staticmethod
|
||||
return parent.objective_groups.all().prefetch_related("hidden_for")
|
||||
|
||||
def resolve_category(self, info, **kwargs):
|
||||
return ModuleCategory.objects.get(pk=self.category_id) if self.category_id else None
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ class PrivateUserNode(DjangoObjectType):
|
|||
"onboarding_visited",
|
||||
"team",
|
||||
"read_only",
|
||||
"last_module_category"
|
||||
]
|
||||
interfaces = (relay.Node,)
|
||||
|
||||
|
|
@ -114,7 +115,6 @@ class PrivateUserNode(DjangoObjectType):
|
|||
is_teacher = graphene.Boolean()
|
||||
old_classes = graphene.List(SchoolClassNode)
|
||||
school_classes = graphene.List(SchoolClassNode)
|
||||
last_module_category = graphene.Field(ModuleCategoryNode)
|
||||
|
||||
recent_modules = DjangoFilterConnectionField(
|
||||
ModuleNode, filterset_class=RecentModuleFilter
|
||||
|
|
@ -163,8 +163,6 @@ class PrivateUserNode(DjangoObjectType):
|
|||
def resolve_team(self, info, **kwargs):
|
||||
return self.team
|
||||
|
||||
def resolve_last_module_category(self, info, **kwargs):
|
||||
return self.last_module_category
|
||||
|
||||
|
||||
class ClassMemberNode(ObjectType):
|
||||
|
|
|
|||
Loading…
Reference in New Issue