- + Bewertungskriterium {{ index + 1 }}: {{ task.value.title }} - + Im Circle anzeigen - + diff --git a/client/src/pages/cockpit/assignmentsPage/AssignmentSubmissionProgress.vue b/client/src/pages/cockpit/assignmentsPage/AssignmentSubmissionProgress.vue index 8b205ad7..3a3aa87a 100644 --- a/client/src/pages/cockpit/assignmentsPage/AssignmentSubmissionProgress.vue +++ b/client/src/pages/cockpit/assignmentsPage/AssignmentSubmissionProgress.vue @@ -44,7 +44,7 @@ onMounted(async () => { {{ props.assignment.title }} - + {{ state.progressStatusCount.success || 0 }} von {{ (state.progressStatusCount.success || 0) + diff --git a/client/src/pages/cockpit/cockpitPage/AssignmentsTile.vue b/client/src/pages/cockpit/cockpitPage/AssignmentsTile.vue index 842c18ab..0db97b66 100644 --- a/client/src/pages/cockpit/cockpitPage/AssignmentsTile.vue +++ b/client/src/pages/cockpit/cockpitPage/AssignmentsTile.vue @@ -34,7 +34,7 @@ const assignments = computed(() => { Geleitete Fallarbeiten - + - {{ $t("assignment.initialSituationTitle") }} - - {{ $t("assignment.taskDefinitionTitle") }} + {{ $t("assignment.taskDefinitionTitle") }} {{ $t("assignment.taskDefinition") }} - - + + - {{ task.value.title }} - {{ $t("assignment.dueDateTitle") }} + {{ $t("assignment.dueDateTitle") }} {{ $t("assignment.dueDateIntroduction", { @@ -53,31 +48,44 @@ const step = useRouteQuery("step"); {{ $t("assignment.dueDateNotSet") }} - {{ $t("assignment.effortTitle") }} - {{ props.assignment.effort_required }} + + {{ $t("assignment.effortTitle") }} + {{ props.assignment.effort_required }} + - - {{ $t("assignment.performanceObjectivesTitle") }} - - - {{ performance_objective.value.text }} - + + + {{ $t("assignment.performanceObjectivesTitle") }} + + + {{ performance_objective.value.text }} + + - {{ $t("assignment.assessmentTitle") }} - - - {{ $t("assignment.showAssessmentDocument") }} - + {{ $t("assignment.assessmentTitle") }} + + + + {{ $t("assignment.showAssessmentDocument") }} + + + diff --git a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue index d452399b..6e46fb48 100644 --- a/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue +++ b/client/src/pages/learningPath/learningContentPage/assignment/AssignmentView.vue @@ -7,6 +7,7 @@ import AssignmentIntroductionView from "@/pages/learningPath/learningContentPage import AssignmentSubmissionView from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue"; import AssignmentTaskView from "@/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue"; import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue"; +import { useCourseSessionsStore } from "@/stores/courseSessions"; import { useUserStore } from "@/stores/user"; import type { Assignment, @@ -75,6 +76,10 @@ onMounted(async () => { props.learningContent ); + state.courseSessionAssignmentDetails = useCourseSessionsStore().findAssignmentDetails( + props.learningContent.id + ); + // create initial `AssignmentCompletion` first, so that it exists and we don't // have reactivity problem accessing it. // noinspection TypeScriptValidateTypes @@ -102,7 +107,12 @@ onMounted(async () => { }); const numTasks = computed(() => assignment.value?.tasks?.length ?? 0); -const numPages = computed(() => numTasks.value + 2); +const numPages = computed(() => { + if (assignmentType.value === "PREP_ASSIGNMENT") { + return numTasks.value + 1; + } + return numTasks.value + 2; +}); const showPreviousButton = computed(() => stepIndex.value != 0); const showNextButton = computed(() => stepIndex.value + 1 < numPages.value); const showExitButton = computed(() => numPages.value === stepIndex.value + 1); @@ -144,17 +154,42 @@ const jumpToTask = (task: AssignmentTask) => { const getTitle = () => { if (0 === stepIndex.value) { return t("general.introduction"); - } else if (stepIndex.value === numPages.value - 1) { + } else if ( + assignmentType.value === "CASEWORK" && + stepIndex.value === numPages.value - 1 + ) { return t("general.submission"); } return currentTask?.value?.value.title ?? "Unknown"; }; +const assignmentType = computed(() => { + return assignment.value?.assignment_type ?? "CASEWORK"; +}); + +const subTitle = computed(() => { + if (assignment.value) { + let prefix = "Geleitete Fallarbeit"; + if (assignmentType.value === "PREP_ASSIGNMENT") { + prefix = "Vorbereitungsauftrag"; + } + return `${prefix}: ${assignment.value?.title ?? ""}`; + } + return ""; +}); + const assignmentUser = computed(() => { return courseSession.value.users.find( (user) => user.user_id === Number(userStore.id) ) as CourseSessionUser; }); + +const endBadgeText = computed(() => { + if (assignmentType.value === "PREP_ASSIGNMENT") { + return "Aufgaben"; + } + return "Abgabe"; +}); @@ -162,7 +197,7 @@ const assignmentUser = computed(() => { { :base-url="props.learningContent.frontend_url" step-query-param="step" start-badge-text="Einleitung" - end-badge-text="Abgabe" + :end-badge-text="endBadgeText" close-button-variant="close" @previous="handleBack()" @next="handleContinue()" @@ -186,13 +221,13 @@ const assignmentUser = computed(() => { :assignment="assignment" > - + {{ props.title }} diff --git a/client/src/services/assignmentService.ts b/client/src/services/assignmentService.ts index e0f1cf53..fa44b688 100644 --- a/client/src/services/assignmentService.ts +++ b/client/src/services/assignmentService.ts @@ -27,7 +27,9 @@ export function calcAssignmentLearningContents(learningPath?: LearningPath) { return learningPath.circles.flatMap((circle) => { const learningContents = circle.flatLearningContents.filter( - (lc) => lc.content_type === "learnpath.LearningContentAssignment" + (lc) => + lc.content_type === "learnpath.LearningContentAssignment" && + lc.assignment_type === "CASEWORK" ) as LearningContentAssignment[]; return learningContents.map((lc) => { return { diff --git a/client/src/types.ts b/client/src/types.ts index 2241804c..47edcf78 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -48,6 +48,7 @@ export interface LearningContentInterface extends BaseCourseWagtailPage { export interface LearningContentAssignment extends LearningContentInterface { readonly content_type: "learnpath.LearningContentAssignment"; readonly content_assignment_id: number; + readonly assignment_type: AssignmentType; } export interface LearningContentAttendanceCourse extends LearningContentInterface { @@ -326,9 +327,12 @@ export interface AssignmentEvaluationTask { }; } +export type AssignmentType = "CASEWORK" | "PREP_ASSIGNMENT" | "REFLECTION"; + export interface Assignment extends BaseCourseWagtailPage { readonly content_type: "assignment.Assignment"; - readonly starting_position: string; + readonly assignment_type: AssignmentType; + readonly intro_text: string; readonly effort_required: string; readonly performance_objectives: AssignmentPerformanceObjective[]; readonly evaluation_description: string; diff --git a/client/src/utils/typeMaps.ts b/client/src/utils/typeMaps.ts index 9a46197b..9a984ce3 100644 --- a/client/src/utils/typeMaps.ts +++ b/client/src/utils/typeMaps.ts @@ -11,7 +11,7 @@ export function learningContentTypeData( ): LearningContentIdentifier { switch (t) { case "learnpath.LearningContentAssignment": - return { title: "Transferauftrag", icon: "it-icon-lc-assignment" }; + return { title: "Geleitete Fallarbeit", icon: "it-icon-lc-assignment" }; case "learnpath.LearningContentAttendanceCourse": return { title: "Präsenzkurs", icon: "it-icon-lc-training" }; case "learnpath.LearningContentLearningModule": diff --git a/client/tailwind.css b/client/tailwind.css index 6a2f8418..f7e61624 100644 --- a/client/tailwind.css +++ b/client/tailwind.css @@ -29,12 +29,18 @@ body { .default-wagtail-rich-text h3 { margin-bottom: 1em; + margin-top: 1em; } .default-wagtail-rich-text p { margin-bottom: 0.5em; } +.default-wagtail-rich-text a { + text-decoration-line: underline; + text-underline-offset: 2px; +} + .default-wagtail-rich-text ul { list-style-type: disc; margin-left: 24px; @@ -129,7 +135,7 @@ textarea { } .btn-secondary { - @apply inline-block border-2 border-blue-900 bg-white px-4 + @apply inline-block border-2 border-blue-900 bg-transparent px-4 py-2 align-middle font-semibold text-blue-900 hover:bg-gray-200 disabled:cursor-not-allowed disabled:opacity-50; diff --git a/server/vbv_lernwelt/assignment/creators/create_assignments.py b/server/vbv_lernwelt/assignment/creators/create_assignments.py index 5f3caa81..5ff1d848 100644 --- a/server/vbv_lernwelt/assignment/creators/create_assignments.py +++ b/server/vbv_lernwelt/assignment/creators/create_assignments.py @@ -27,13 +27,16 @@ def create_uk_casework(assignment_list_page, course_id=COURSE_UK): parent=assignment_list_page, title="Überprüfen einer Motorfahrzeugs-Versicherungspolice", effort_required="ca. 5 Stunden", - starting_position=replace_whitespace( + intro_text=replace_whitespace( """ + Ausgangslage + Jemand aus deiner Familie oder aus deinem Freundeskreis möchte sein Versicherungspolice überprüfen lassen. Diese Person kommt nun mit ihrer Police auf dich zu und bittet dich als Versicherungsprofi, diese kritisch zu überprüfen und ihr ggf. Anpassungsvorschläge zu unterbreiten. In diesem Kompetenznachweis kannst du nun dein Wissen und Können im Bereich der Motorfahrzeugversicherung unter Beweis stellen. + """ ), performance_objectives=[ @@ -466,7 +469,7 @@ def create_test_assignment(course_id=COURSE_TEST_ID): parent=assignment_page, title="Überprüfen einer Motorfahrzeugs-Versicherungspolice", effort_required="ca. 5 Stunden", - starting_position=replace_whitespace( + intro_text=replace_whitespace( """ Jemand aus deiner Familie oder aus deinem Freundeskreis möchte sein Versicherungspolice überprüfen lassen. Diese Person kommt nun mit ihrer Police auf dich zu @@ -901,7 +904,7 @@ def create_uk_prep_assignment(assignment_list_page, course_id=COURSE_UK): assignment_type=AssignmentType.PREP_ASSIGNMENT.name, title="Fahrzeug - Mein erstes Auto", effort_required="ca. 3 Stunden", - starting_position=replace_whitespace( + intro_text=replace_whitespace( """ Handlungskompetenzen, Arbeitssituationen & Leistungsziele @@ -1128,6 +1131,76 @@ def create_uk_prep_assignment(assignment_list_page, course_id=COURSE_UK): ) ) + assignment.tasks.append( + ( + "task", + TaskBlockFactory( + title="Aufgaben zum Vorbereitungsauftrag", + content=StreamValue( + TaskContentStreamBlock(), + stream_data=[ + ( + "explanation", + ExplanationBlockFactory( + text=RichText( + "Schnappt euch euren Vorbereitungsauftrag und setzt euch zu dritt zusammen. Diskutiert miteinander die folgenden Fragen." + ) + ), + ), + ( + "user_text_input", + UserTextInputBlockFactory( + text=RichText( + "Wie seid ihr bei der Erstellung der Offerte vorgegangen?" + ) + ), + ), + ( + "user_text_input", + UserTextInputBlockFactory( + text=RichText( + "Welches waren die Schwierigkeiten bei der Erstellung der Offerte? Wie hast du die Schwierigkeiten gelöst?" + ) + ), + ), + ( + "user_text_input", + UserTextInputBlockFactory( + text=RichText( + "Welche Angaben sind zwingend notwendig, um eine saubere Motorfahrzeugofferte erstellen zu können?" + ) + ), + ), + ( + "user_text_input", + UserTextInputBlockFactory( + text=RichText( + "Welche zusätzlichen Deckungen hast du gewählt? Was waren die Überlegungen dazu?" + ) + ), + ), + ( + "user_text_input", + UserTextInputBlockFactory( + text=RichText( + "Welche Faktoren/Elemente bestimmen hauptsächlich die Höhe der Prämie?" + ) + ), + ), + ( + "user_text_input", + UserTextInputBlockFactory( + text=RichText( + "Wenn ihr mit der Diskussion und dem Vergleich fertig seid, schreibt doch die Prämie eurer eigenen Offerte jeweils auf den Flipchart/Whiteboard/Wandtafel." + ) + ), + ), + ], + ), + ), + ) + ) + assignment.save() return assignment diff --git a/server/vbv_lernwelt/assignment/graphql/types.py b/server/vbv_lernwelt/assignment/graphql/types.py index f33cd6e8..9d33971b 100644 --- a/server/vbv_lernwelt/assignment/graphql/types.py +++ b/server/vbv_lernwelt/assignment/graphql/types.py @@ -15,7 +15,8 @@ class AssignmentType(DjangoObjectType): model = Assignment interfaces = (CoursePageInterface,) fields = ( - "starting_position", + "assignment_type", + "intro_text", "effort_required", "evaluation_description", "evaluation_document_url", diff --git a/server/vbv_lernwelt/assignment/migrations/0001_initial.py b/server/vbv_lernwelt/assignment/migrations/0001_initial.py index ac598681..c40d150d 100644 --- a/server/vbv_lernwelt/assignment/migrations/0001_initial.py +++ b/server/vbv_lernwelt/assignment/migrations/0001_initial.py @@ -7,7 +7,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - initial = True dependencies = [ @@ -30,7 +29,7 @@ class Migration(migrations.Migration): ), ), ( - "starting_position", + "intro_text", wagtail.fields.RichTextField( help_text="Erläuterung der Ausgangslage" ), diff --git a/server/vbv_lernwelt/assignment/models.py b/server/vbv_lernwelt/assignment/models.py index fe879ed5..d482a175 100644 --- a/server/vbv_lernwelt/assignment/models.py +++ b/server/vbv_lernwelt/assignment/models.py @@ -119,7 +119,7 @@ AssignmentType = Enum( class Assignment(CourseBasePage): serialize_field_names = [ - "starting_position", + "intro_text", "effort_required", "performance_objectives", "evaluation_description", @@ -134,7 +134,7 @@ class Assignment(CourseBasePage): default=AssignmentType.CASEWORK.name, ) - starting_position = RichTextField( + intro_text = RichTextField( help_text="Erläuterung der Ausgangslage", features=DEFAULT_RICH_TEXT_FEATURES_WITH_HEADER, ) @@ -182,7 +182,7 @@ class Assignment(CourseBasePage): content_panels = Page.content_panels + [ FieldPanel("assignment_type"), - FieldPanel("starting_position"), + FieldPanel("intro_text"), FieldPanel("effort_required"), FieldPanel("performance_objectives"), FieldPanel("tasks"), diff --git a/server/vbv_lernwelt/assignment/tests/assignment_factories.py b/server/vbv_lernwelt/assignment/tests/assignment_factories.py index 2dcfaaa0..97723d20 100644 --- a/server/vbv_lernwelt/assignment/tests/assignment_factories.py +++ b/server/vbv_lernwelt/assignment/tests/assignment_factories.py @@ -82,7 +82,7 @@ class PerformanceObjectiveBlockFactory(wagtail_factories.StructBlockFactory): class AssignmentFactory(wagtail_factories.PageFactory): title = "Auftrag" - starting_position = replace_whitespace( + intro_text = replace_whitespace( """ Jemand aus deiner Familie oder aus deinem Freundeskreis möchte sein Versicherungspolice überprüfen lassen. Diese Person kommt nun mit ihrer Police auf dich zu diff --git a/server/vbv_lernwelt/course/creators/test_course.py b/server/vbv_lernwelt/course/creators/test_course.py index d06fc7e7..41328c20 100644 --- a/server/vbv_lernwelt/course/creators/test_course.py +++ b/server/vbv_lernwelt/course/creators/test_course.py @@ -225,10 +225,14 @@ damit du erfolgreich mit deinem Lernpfad (durch-)starten kannst. ), content_url=f"/course/{lp.get_course().slug}/media/category/{slugify(title)}", ) - LearningContentPlaceholderFactory( - title="Vorbereitungsauftrag", + LearningContentAssignmentFactory( + title="Fahrzeug - Mein erstes Auto", + assignment_type="PREP_ASSIGNMENT", parent=circle, - ) + content_assignment=Assignment.objects.get( + slug__startswith="überbetriebliche-kurse-assignment-fahrzeug-mein-erstes-auto" + ), + ), PerformanceCriteriaFactory( parent=CompetencePage.objects.get(competence_id="X1"), diff --git a/server/vbv_lernwelt/course/management/commands/create_default_courses.py b/server/vbv_lernwelt/course/management/commands/create_default_courses.py index 5a10ee14..ba6b3cc7 100644 --- a/server/vbv_lernwelt/course/management/commands/create_default_courses.py +++ b/server/vbv_lernwelt/course/management/commands/create_default_courses.py @@ -187,7 +187,14 @@ def create_course_uk_de(): ).id, "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z", "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", - } + }, + { + "learningContentId": LearningContentAssignment.objects.get( + slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-fahrzeug-mein-erstes-auto" + ).id, + "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z", + "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z", + }, ], ) diff --git a/server/vbv_lernwelt/course/management/commands/create_uk_course.py b/server/vbv_lernwelt/course/management/commands/create_uk_course.py index cfb3bce5..9ef01b51 100644 --- a/server/vbv_lernwelt/course/management/commands/create_uk_course.py +++ b/server/vbv_lernwelt/course/management/commands/create_uk_course.py @@ -243,10 +243,14 @@ damit du erfolgreich mit deinem Lernpfad (durch-)starten kannst. ), content_url=f"/course/überbetriebliche-kurse/media/category/{slugify(title)}", ) - LearningContentPlaceholderFactory( - title="Vorbereitungsauftrag", + LearningContentAssignmentFactory( + title="Fahrzeug - Mein erstes Auto", + assignment_type="PREP_ASSIGNMENT", parent=circle, - ) + content_assignment=Assignment.objects.get( + slug__startswith="überbetriebliche-kurse-assignment-fahrzeug-mein-erstes-auto" + ), + ), LearningSequenceFactory(title="Training", parent=circle) LearningUnitFactory(title="Unterlagen", parent=circle) LearningContentPlaceholderFactory( diff --git a/server/vbv_lernwelt/learnpath/migrations/0003_learningcontentassignment_assignment_type.py b/server/vbv_lernwelt/learnpath/migrations/0003_learningcontentassignment_assignment_type.py new file mode 100644 index 00000000..cc0b9e5d --- /dev/null +++ b/server/vbv_lernwelt/learnpath/migrations/0003_learningcontentassignment_assignment_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.13 on 2023-05-19 15:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('learnpath', '0002_learningcontentrichtext_text'), + ] + + operations = [ + migrations.AddField( + model_name='learningcontentassignment', + name='assignment_type', + field=models.CharField(choices=[('CASEWORK', 'CASEWORK'), ('PREP_ASSIGNMENT', 'PREP_ASSIGNMENT'), ('REFLECTION', 'REFLECTION')], default='CASEWORK', max_length=50), + ), + ] diff --git a/server/vbv_lernwelt/learnpath/models.py b/server/vbv_lernwelt/learnpath/models.py index 29c25b3a..2e040764 100644 --- a/server/vbv_lernwelt/learnpath/models.py +++ b/server/vbv_lernwelt/learnpath/models.py @@ -6,6 +6,7 @@ from wagtail.admin.panels import FieldPanel, PageChooserPanel from wagtail.fields import RichTextField from wagtail.models import Page +from vbv_lernwelt.assignment.models import AssignmentType from vbv_lernwelt.core.constants import DEFAULT_RICH_TEXT_FEATURES_WITH_HEADER from vbv_lernwelt.core.model_utils import find_available_slug from vbv_lernwelt.course.models import CourseBasePage, CoursePage @@ -322,6 +323,7 @@ class LearningContentRichText(LearningContent): class LearningContentAssignment(LearningContent): serialize_field_names = LearningContent.serialize_field_names + [ "content_assignment_id", + "assignment_type", ] parent_page_types = ["learnpath.Circle"] subpage_types = [] @@ -332,6 +334,12 @@ class LearningContentAssignment(LearningContent): related_name="+", ) + assignment_type = models.CharField( + max_length=50, + choices=[(tag.name, tag.name) for tag in AssignmentType], + default=AssignmentType.CASEWORK.name, + ) + content_panels = [ FieldPanel("title", classname="full title"), FieldPanel("minutes"),