Add Category Pills to Module

This commit is contained in:
Lorenz Padberg 2023-08-15 15:32:07 +02:00
parent 475afd03ed
commit 6d312da0ae
9 changed files with 240 additions and 177 deletions

View File

@ -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>

View File

@ -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);

View File

@ -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>

View File

@ -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,

View File

@ -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>

View File

@ -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: {

View File

@ -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)

View File

@ -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

View File

@ -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):