Merged in feature/VBV-304-praesenztag-ui (pull request #65)

Feature/VBV-304 praesenztag ui

Approved-by: Daniel Egger
This commit is contained in:
Ramon Wenger 2023-04-26 09:57:22 +00:00
commit 96334b4eb6
20 changed files with 158 additions and 57 deletions

View File

@ -0,0 +1,26 @@
<template>
<div class="mb-12 grid grid-cols-icon-card gap-x-4 grid-areas-icon-card">
<it-icon-calendar class="w-[60px] grid-in-icon" />
<h2 class="text-large font-bold grid-in-title">Datum</h2>
<p class="grid-in-value">{{ attendanceDay.date }}</p>
</div>
<div class="mb-12 grid grid-cols-icon-card gap-x-4 grid-areas-icon-card">
<it-icon-location class="w-[60px] grid-in-icon" />
<h2 class="text-large font-bold grid-in-title">Standort</h2>
<p class="grid-in-value">{{ attendanceDay.location }}</p>
</div>
<div class="grid grid-cols-icon-card content-between gap-x-4 grid-areas-icon-card">
<it-icon-trainer class="w-[60px] grid-in-icon" />
<h2 class="text-large font-bold grid-in-title">Trainer</h2>
<p class="grid-in-value">{{ attendanceDay.trainer }}</p>
</div>
</template>
<script setup lang="ts">
import type { CourseSessionAttendanceDay } from "@/types";
export interface Props {
attendanceDay: CourseSessionAttendanceDay;
}
defineProps<Props>();
</script>

View File

@ -2,13 +2,15 @@
import { useCourseSessionsStore } from "@/stores/courseSessions"; import { useCourseSessionsStore } from "@/stores/courseSessions";
import type { LearningContent } from "@/types"; import type { LearningContent } from "@/types";
import { computed } from "vue"; import { computed } from "vue";
import AttendanceDay from "../attendanceDay/AttendanceDay.vue";
import LearningContentSimpleLayout from "../layouts/LearningContentSimpleLayout.vue";
const courseSessionsStore = useCourseSessionsStore(); export interface Value {
interface Value {
description: string; description: string;
} }
const courseSessionsStore = useCourseSessionsStore();
const props = defineProps<{ const props = defineProps<{
value: Value; value: Value;
content: LearningContent; content: LearningContent;
@ -20,12 +22,21 @@ const attendanceDay = computed(() => {
</script> </script>
<template> <template>
<div class="container-medium"> <LearningContentSimpleLayout
<div class="lg:mt-8"> :title="content.title"
<div class="text-large my-4"> learning-content-type="attendance_day"
<div v-if="attendanceDay">{{ attendanceDay }}</div> >
<div v-else>Für diese Durchführung existieren noch keine Details</div> <div class="container-medium">
<div class="lg:mt-8">
<div class="text-large my-4">
<div v-if="attendanceDay">
<AttendanceDay :attendance-day="attendanceDay" />
</div>
<div v-else>
Für diese Durchführung {{ content.id }} existieren noch keine Details
</div>
</div>
</div> </div>
</div> </div>
</div> </LearningContentSimpleLayout>
</template> </template>

View File

@ -1,8 +1,5 @@
<template> <template>
<LearningContentSimpleLayout <LearningContentSimpleLayout learning-content-type="resource">
:subtitle="learningContentTypeData('resource').title"
:learning-content-type="'resource'"
>
<div class="h-screen"> <div class="h-screen">
<iframe width="100%" height="100%" scrolling="no" :src="value.url" /> <iframe width="100%" height="100%" scrolling="no" :src="value.url" />
</div> </div>
@ -12,13 +9,12 @@
<script setup lang="ts"> <script setup lang="ts">
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue"; import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
import type { LearningContent } from "@/types"; import type { LearningContent } from "@/types";
import { learningContentTypeData } from "@/utils/typeMaps";
interface Value { export interface Value {
url: string; url: string;
} }
const props = defineProps<{ defineProps<{
value: Value; value: Value;
content: LearningContent; content: LearningContent;
}>(); }>();

View File

@ -1,8 +1,7 @@
<template> <template>
<LearningContentSimpleLayout <LearningContentSimpleLayout
:title="content.title" :title="content.title"
:subtitle="learningContentTypeData('media_library').title" learning-content-type="media_library"
:learning-content-type="'media_library'"
> >
<div class="container-medium"> <div class="container-medium">
<p class="text-large my-4 lg:my-8">{{ value.description }}</p> <p class="text-large my-4 lg:my-8">{{ value.description }}</p>
@ -16,16 +15,15 @@
<script setup lang="ts"> <script setup lang="ts">
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue"; import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
import type { LearningContent } from "@/types"; import type { LearningContent } from "@/types";
import { learningContentTypeData } from "@/utils/typeMaps";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); export interface Value {
interface Value {
description: string; description: string;
url: string; url: string;
} }
const route = useRoute();
defineProps<{ defineProps<{
value: Value; value: Value;
content: LearningContent; content: LearningContent;

View File

@ -1,21 +1,19 @@
<template> <template>
<LearningContentSimpleLayout <LearningContentSimpleLayout
:title="content.title" :title="content.title"
:subtitle="learningContentTypeData('placeholder').title" learning-content-type="placeholder"
:learning-content-type="'placeholder'"
></LearningContentSimpleLayout> ></LearningContentSimpleLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue"; import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
import type { LearningContent } from "@/types"; import type { LearningContent } from "@/types";
import { learningContentTypeData } from "@/utils/typeMaps";
interface Value { export interface Value {
description: string; description: string;
} }
const props = defineProps<{ defineProps<{
value: Value; value: Value;
content: LearningContent; content: LearningContent;
}>(); }>();

View File

@ -1,9 +1,5 @@
<template> <template>
<LearningContentSimpleLayout <LearningContentSimpleLayout :title="content.title" learning-content-type="video">
:title="content.title"
:subtitle="learningContentTypeData('video').title"
:learning-content-type="'video'"
>
<div class="container-medium"> <div class="container-medium">
<iframe <iframe
class="mt-8 aspect-video w-full" class="mt-8 aspect-video w-full"
@ -20,13 +16,12 @@
<script setup lang="ts"> <script setup lang="ts">
import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue"; import LearningContentSimpleLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentSimpleLayout.vue";
import type { LearningContent } from "@/types"; import type { LearningContent } from "@/types";
import { learningContentTypeData } from "@/utils/typeMaps";
interface Value { export interface Value {
url: string; url: string;
} }
const props = defineProps<{ defineProps<{
value: Value; value: Value;
content: LearningContent; content: LearningContent;
}>(); }>();

View File

@ -4,15 +4,16 @@ import LearningContentFooter from "@/pages/learningPath/learningContentPage/layo
import type { LearningContentType } from "@/types"; import type { LearningContentType } from "@/types";
import { learningContentTypeData } from "@/utils/typeMaps"; import { learningContentTypeData } from "@/utils/typeMaps";
interface Props { export interface Props {
title: string | undefined; title: string;
subtitle: string;
learningContentType: LearningContentType; learningContentType: LearningContentType;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
title: undefined, title: "",
}); });
const type = learningContentTypeData(props.learningContentType);
</script> </script>
<template> <template>
@ -21,12 +22,9 @@ const props = withDefaults(defineProps<Props>(), {
v-if="props.learningContentType !== 'placeholder'" v-if="props.learningContentType !== 'placeholder'"
class="flex h-min w-full items-center gap-2 pb-8" class="flex h-min w-full items-center gap-2 pb-8"
> >
<component <component :is="type.icon" class="h-6 w-6 text-gray-900"></component>
:is="learningContentTypeData(props.learningContentType).icon"
class="h-6 w-6 text-gray-900"
></component>
<p class="whitespace-nowrap text-gray-900"> <p class="whitespace-nowrap text-gray-900">
{{ props.subtitle }} {{ type.title }}
</p> </p>
</div> </div>

View File

@ -1,10 +1,14 @@
import type { LearningContentType } from "@/types"; import type { LearningContentType } from "@/types";
import { assertUnreachable } from "@/utils/utils"; import { assertUnreachable } from "@/utils/utils";
export function learningContentTypeData(t: LearningContentType): { export interface LearningContentIdentifier {
title: string; title: string;
icon: string; icon: string;
} { }
export function learningContentTypeData(
t: LearningContentType
): LearningContentIdentifier {
switch (t) { switch (t) {
case "assignment": case "assignment":
return { title: "Transferauftrag", icon: "it-icon-lc-assignment" }; return { title: "Transferauftrag", icon: "it-icon-lc-assignment" };
@ -29,7 +33,7 @@ export function learningContentTypeData(t: LearningContentType): {
case "feedback": case "feedback":
return { title: "Feedback", icon: "it-icon-lc-feedback" }; return { title: "Feedback", icon: "it-icon-lc-feedback" };
case "attendance_day": case "attendance_day":
return { title: "Feedback", icon: "it-icon-lc-exercise" }; return { title: "Präsenztag", icon: "it-icon-lc-training" };
case "placeholder": case "placeholder":
return { title: "In Umsetzung", icon: "it-icon-lc-document" }; return { title: "In Umsetzung", icon: "it-icon-lc-document" };
} }

View File

@ -50,10 +50,12 @@ module.exports = {
"fst fst snd snd trd trd fth fth", "fst fst snd snd trd trd fth fth",
], ],
"rating-scale-slim": ["bar bar bar", "fst mid fth"], "rating-scale-slim": ["bar bar bar", "fst mid fth"],
"icon-card": ["icon title", "icon value"],
}, },
gridTemplateColumns: { gridTemplateColumns: {
"horizontal-bar-chart": "50px 1fr 300px 4fr 300px 1fr", "horizontal-bar-chart": "50px 1fr 300px 4fr 300px 1fr",
"horizontal-bar-chart-slim": "50px 1fr 78px 4fr 78px 1fr", "horizontal-bar-chart-slim": "50px 1fr 78px 4fr 78px 1fr",
"icon-card": "60px auto",
}, },
gridTemplateRows: { gridTemplateRows: {
"horizontal-bar-chart": "200px 40px", "horizontal-bar-chart": "200px 40px",

View File

@ -101,6 +101,7 @@ THIRD_PARTY_APPS = [
"grapple", "grapple",
"graphene_django", "graphene_django",
"notifications", "notifications",
"django_jsonform",
] ]
LOCAL_APPS = [ LOCAL_APPS = [

View File

@ -1,6 +1,6 @@
# #
# This file is autogenerated by pip-compile with python 3.10 # This file is autogenerated by pip-compile with Python 3.10
# To update, run: # by the following command:
# #
# pip-compile --output-file=requirements-dev.txt requirements-dev.in # pip-compile --output-file=requirements-dev.txt requirements-dev.in
# #
@ -10,8 +10,6 @@ anyascii==0.3.1
# via wagtail # via wagtail
anyio==3.5.0 anyio==3.5.0
# via watchfiles # via watchfiles
appnope==0.1.3
# via ipython
argon2-cffi==21.3.0 argon2-cffi==21.3.0
# via -r requirements.in # via -r requirements.in
argon2-cffi-bindings==21.2.0 argon2-cffi-bindings==21.2.0
@ -106,6 +104,7 @@ django==3.2.13
# django-debug-toolbar # django-debug-toolbar
# django-extensions # django-extensions
# django-filter # django-filter
# django-jsonform
# django-model-utils # django-model-utils
# django-modelcluster # django-modelcluster
# django-notifications-hq # django-notifications-hq
@ -140,6 +139,8 @@ django-filter==21.1
# via wagtail # via wagtail
django-ipware==4.0.2 django-ipware==4.0.2
# via -r requirements.in # via -r requirements.in
django-jsonform==2.17.0
# via -r requirements.in
django-model-utils==4.2.0 django-model-utils==4.2.0
# via # via
# -r requirements.in # -r requirements.in

View File

@ -26,6 +26,7 @@ django-ipware
django-csp django-csp
django-storages django-storages
django-notifications-hq django-notifications-hq
django-jsonform
psycopg2-binary psycopg2-binary
gunicorn gunicorn

View File

@ -1,6 +1,6 @@
# #
# This file is autogenerated by pip-compile with python 3.10 # This file is autogenerated by pip-compile with Python 3.10
# To update, run: # by the following command:
# #
# pip-compile --output-file=requirements.txt requirements.in # pip-compile --output-file=requirements.txt requirements.in
# #
@ -62,6 +62,7 @@ django==3.2.13
# django-cors-headers # django-cors-headers
# django-csp # django-csp
# django-filter # django-filter
# django-jsonform
# django-model-utils # django-model-utils
# django-modelcluster # django-modelcluster
# django-notifications-hq # django-notifications-hq
@ -87,6 +88,8 @@ django-filter==21.1
# via wagtail # via wagtail
django-ipware==4.0.2 django-ipware==4.0.2
# via -r requirements.in # via -r requirements.in
django-jsonform==2.17.0
# via -r requirements.in
django-model-utils==4.2.0 django-model-utils==4.2.0
# via # via
# -r requirements.in # -r requirements.in

View File

@ -8,9 +8,6 @@ from vbv_lernwelt.learnpath.models import Circle
@admin.register(CourseSession) @admin.register(CourseSession)
class CourseSessionAdmin(admin.ModelAdmin): class CourseSessionAdmin(admin.ModelAdmin):
formfield_overrides = {
JSONField: {"widget": PrettyJSONWidget(attrs={"rows": 16, "cols": 80})},
}
date_hierarchy = "created_at" date_hierarchy = "created_at"
list_display = [ list_display = [
"title", "title",

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.13 on 2023-04-25 12:28
import django_jsonform.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("course", "0004_coursesession_assignment_details_list"),
]
operations = [
migrations.AlterField(
model_name="coursesession",
name="attendance_days",
field=django_jsonform.models.fields.JSONField(),
),
]

View File

@ -2,6 +2,7 @@ from django.db import models
from django.db.models import UniqueConstraint from django.db.models import UniqueConstraint
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_jsonform.models.fields import JSONField
from wagtail.models import Page from wagtail.models import Page
from vbv_lernwelt.core.model_utils import find_available_slug from vbv_lernwelt.core.model_utils import find_available_slug
@ -178,6 +179,23 @@ class CourseSession(models.Model):
Das anhängen kann via CourseSessionUser oder "Schulklasse (TODO)" geschehen Das anhängen kann via CourseSessionUser oder "Schulklasse (TODO)" geschehen
""" """
ATTENDANCE_DAYS_SCHEMA = {
"type": "array",
"items": {
"type": "object",
"properties": {
"learningContentId": {
"type": "number",
"title": "ID des Lerninhalts",
"required": True,
},
"date": {"type": "string"},
"location": {"type": "string"},
"trainer": {"type": "string"},
},
},
}
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
@ -187,7 +205,7 @@ class CourseSession(models.Model):
start_date = models.DateField(null=True, blank=True) start_date = models.DateField(null=True, blank=True)
end_date = models.DateField(null=True, blank=True) end_date = models.DateField(null=True, blank=True)
attendance_days = models.JSONField(default=list, blank=True) attendance_days = JSONField(schema=ATTENDANCE_DAYS_SCHEMA, blank=True, default=list)
assignment_details_list = models.JSONField(default=list, blank=True) assignment_details_list = models.JSONField(default=list, blank=True)
additional_json_data = models.JSONField(default=dict, blank=True) additional_json_data = models.JSONField(default=dict, blank=True)

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Outline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path d="m25.18,4.86H4.82c-1.24,0-2.25,1.01-2.25,2.25v15.77c0,1.24,1.01,2.25,2.25,2.25h20.36c1.24,0,2.25-1.01,2.25-2.25V7.11c0-1.24-1.01-2.25-2.25-2.25Zm-20.36,1.5h20.36c.41,0,.75.34.75.75v2.21H4.07v-2.21c0-.41.34-.75.75-.75Zm20.36,17.27H4.82c-.41,0-.75-.34-.75-.75v-12.06h21.86v12.06c0,.41-.34.75-.75.75Z"/>
<rect x="6.17" y="13.25" width="2.67" height="2.67"/>
<rect x="11.18" y="13.25" width="2.67" height="2.67"/>
<rect x="16.19" y="13.25" width="2.67" height="2.67"/>
<rect x="6.17" y="18.26" width="2.67" height="2.67"/>
<rect x="11.18" y="18.26" width="2.67" height="2.67"/>
<rect x="16.19" y="18.26" width="2.67" height="2.67"/>
<rect x="21.2" y="13.25" width="2.67" height="2.67"/>
</svg>

After

Width:  |  Height:  |  Size: 826 B

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Outline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path d="m26.48.94h-12.15c-1.24,0-2.25,1.01-2.25,2.25v5.54H3.52c-1.24,0-2.25,1.01-2.25,2.25v7.83h1.5v-7.83c0-.41.34-.75.75-.75h8.56v5.11c0,1.24,1.01,2.25,2.25,2.25h12.15c1.24,0,2.25-1.01,2.25-2.25V3.19c0-1.24-1.01-2.25-2.25-2.25Zm.75,14.4c0,.41-.34.75-.75.75h-12.15c-.41,0-.75-.34-.75-.75v-5.11h3.08v-1.5h-3.08V3.19c0-.41.34-.75.75-.75h12.15c.41,0,.75.34.75.75v12.15Z"/>
<path d="m6.85,7.21c1.33,0,2.41-1.08,2.41-2.41s-1.08-2.41-2.41-2.41-2.41,1.08-2.41,2.41,1.08,2.41,2.41,2.41Zm0-3.31c.5,0,.91.41.91.91s-.41.91-.91.91-.91-.41-.91-.91.41-.91.91-.91Z"/>
<path d="m14.48,19.35c-1.33,0-2.41,1.08-2.41,2.41s1.08,2.41,2.41,2.41,2.41-1.08,2.41-2.41-1.08-2.41-2.41-2.41Zm0,3.31c-.5,0-.91-.41-.91-.91s.41-.91.91-.91.91.41.91.91-.41.91-.91.91Z"/>
<path d="m20.41,19.35c-1.33,0-2.41,1.08-2.41,2.41s1.08,2.41,2.41,2.41,2.41-1.08,2.41-2.41-1.08-2.41-2.41-2.41Zm0,3.31c-.5,0-.91-.41-.91-.91s.41-.91.91-.91.91.41.91.91-.41.91-.91.91Z"/>
<path d="m26.33,19.35c-1.33,0-2.41,1.08-2.41,2.41s1.08,2.41,2.41,2.41,2.41-1.08,2.41-2.41-1.08-2.41-2.41-2.41Zm0,3.31c-.5,0-.91-.41-.91-.91s.41-.91.91-.91.91.41.91.91-.41.91-.91.91Z"/>
<rect x="8.3" y="12.26" width="1.5" height="16.06"/>
<rect x="4.44" y="19.28" width="1.5" height="9.03"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Outline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path d="m15,28.18c-.17,0-.34-.06-.48-.17-4.95-4.06-8.69-11.2-8.69-16.59S9.78,1.82,15,1.82s9.16,4.12,9.16,9.59-3.74,12.53-8.69,16.59c-.14.11-.31.17-.48.17Zm0-24.85c-4.44,0-7.66,3.4-7.66,8.09s3.33,11.21,7.66,15.02c4.33-3.81,7.66-10.29,7.66-15.02s-3.22-8.09-7.66-8.09Z"/>
<path d="m15,14.47c-1.92,0-3.47-1.56-3.47-3.47s1.56-3.47,3.47-3.47,3.47,1.56,3.47,3.47-1.56,3.47-3.47,3.47Zm0-5.45c-1.09,0-1.97.88-1.97,1.97s.88,1.97,1.97,1.97,1.97-.88,1.97-1.97-.88-1.97-1.97-1.97Z"/>
</svg>

After

Width:  |  Height:  |  Size: 595 B

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Outline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path d="m12.51,6.48c-1.33,0-2.41-1.08-2.41-2.41s1.08-2.41,2.41-2.41,2.41,1.08,2.41,2.41-1.08,2.41-2.41,2.41Zm0-3.31c-.5,0-.91.41-.91.91s.41.91.91.91.91-.41.91-.91-.41-.91-.91-.91Z"/>
<path d="m8.43,18.08h-1.5v-7.82c0-1.24,1.01-2.25,2.25-2.25h13.15v1.5h-13.15c-.41,0-.75.34-.75.75v7.82Z"/>
<rect x="13.96" y="11.53" width="1.5" height="16.06"/>
<rect x="10.1" y="18.55" width="1.5" height="9.03"/>
</svg>

After

Width:  |  Height:  |  Size: 525 B