Merged in feature/VBV-354-termine (pull request #155)
Feature/VBV-354 termine Approved-by: Daniel Egger
This commit is contained in:
commit
7e8773cd17
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { getDateString, getTimeString } from "@/components/dueDates/dueDatesUtils";
|
||||||
|
import type { Dayjs } from "dayjs";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
singleDate?: Dayjs;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<it-icon-calendar-light class="it-icon h-5 w-5" />
|
||||||
|
{{ getDateString(props.singleDate) }}, {{ getTimeString(props.singleDate) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { formatDate } from "@/components/dueDates/dueDatesUtils";
|
||||||
|
import type { DueDate } from "@/types";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
dueDate: DueDate;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center justify-between py-4">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<a class="text-bold underline" :href="props.dueDate.url" target="_blank">
|
||||||
|
{{ props.dueDate.title }}
|
||||||
|
</a>
|
||||||
|
<p class="text-small text-gray-900">
|
||||||
|
{{ props.dueDate.learning_content_description }}
|
||||||
|
<span v-if="props.dueDate.description !== ''">:</span>
|
||||||
|
{{ props.dueDate.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{{ formatDate(props.dueDate.start, props.dueDate.end) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="dueDate in dueDatesDisplayed"
|
||||||
|
:key="dueDate.id"
|
||||||
|
:class="{ 'first:border-t': props.showTopBorder, 'border-b': true }"
|
||||||
|
>
|
||||||
|
<DueDateSingle :due-date="dueDate"></DueDateSingle>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div v-if="allDueDates.length > props.maxCount" class="flex items-center pt-6">
|
||||||
|
<a href="">{{ $t("dueDates.showAllDueDates") }}</a>
|
||||||
|
<it-icon-arrow-right />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="allDueDates.length === 0">{{ $t("dueDates.noDueDatesAvailable") }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import DueDateSingle from "@/components/dueDates/DueDateSingle.vue";
|
||||||
|
import type { DueDate } from "@/types";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
maxCount: number;
|
||||||
|
dueDates: DueDate[];
|
||||||
|
showTopBorder: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const allDueDates = computed(() => {
|
||||||
|
return props.dueDates;
|
||||||
|
});
|
||||||
|
|
||||||
|
const dueDatesDisplayed = computed(() => {
|
||||||
|
return props.dueDates.slice(0, props.maxCount);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<DueDatesList
|
||||||
|
:due-dates="allDueDates"
|
||||||
|
:max-count="props.maxCount"
|
||||||
|
:show-top-border="props.showTopBorder"
|
||||||
|
></DueDatesList>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
||||||
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
maxCount: number;
|
||||||
|
showTopBorder: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const courseSession = useCurrentCourseSession();
|
||||||
|
const allDueDates = courseSession.value.due_dates;
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
export const dueDatesTestData = () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
start: dayjs("2023-06-14T15:00:00+02:00"),
|
||||||
|
end: dayjs("2023-06-14T18:00:00+02:00"),
|
||||||
|
title: "Präsenzkurs Kickoff",
|
||||||
|
url: "/course/überbetriebliche-kurse/learn/kickoff/präsenzkurs-kickoff",
|
||||||
|
course_session: 2,
|
||||||
|
page: 383,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
start: dayjs("2023-06-15T15:00:00+02:00"),
|
||||||
|
end: dayjs("2023-06-15T18:00:00+02:00"),
|
||||||
|
title: "Präsenzkurs Basis",
|
||||||
|
url: "/course/überbetriebliche-kurse/learn/basis/präsenzkurs-basis",
|
||||||
|
course_session: 2,
|
||||||
|
page: 397,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
start: dayjs("2023-06-16T15:00:00+02:00"),
|
||||||
|
end: dayjs("2023-06-16T18:00:00+02:00"),
|
||||||
|
title: "Präsenzkurs Fahrzeug",
|
||||||
|
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
||||||
|
course_session: 2,
|
||||||
|
page: 413,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
start: dayjs("2023-06-16T15:00:00+02:00"),
|
||||||
|
end: dayjs("2023-06-16T18:00:00+02:00"),
|
||||||
|
title: "Präsenzkurs Flugzeuge",
|
||||||
|
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
||||||
|
course_session: 2,
|
||||||
|
page: 413,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
start: dayjs("2023-07-16T11:00:00+02:00"),
|
||||||
|
end: dayjs("2023-07-16T18:00:00+02:00"),
|
||||||
|
title: "Präsenzkurs Motorräder",
|
||||||
|
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
||||||
|
course_session: 2,
|
||||||
|
page: 413,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
start: dayjs("2023-08-09T15:00:00+02:00"),
|
||||||
|
end: dayjs("2023-08-09T19:00:00+02:00"),
|
||||||
|
title: "Präsenzkurs Fahrräder",
|
||||||
|
url: "/course/überbetriebliche-kurse/learn/fahrzeug/präsenzkurs-fahrzeug",
|
||||||
|
course_session: 2,
|
||||||
|
page: 413,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import type { Dayjs } from "dayjs";
|
||||||
|
|
||||||
|
export const formatDate = (start: Dayjs, end: Dayjs) => {
|
||||||
|
const startDateString = getDateString(start);
|
||||||
|
const endDateString = getDateString(end);
|
||||||
|
|
||||||
|
// if start isundefined, dont show the day twice
|
||||||
|
|
||||||
|
if (!start.isValid() && !end.isValid()) {
|
||||||
|
return "Termin nicht festgelegt";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!start || (!start.isValid() && end.isValid())) {
|
||||||
|
return `${endDateString} ${getTimeString(end)} ${end.format("[Uhr]")}`;
|
||||||
|
}
|
||||||
|
if (!end || (!end.isValid() && start.isValid())) {
|
||||||
|
return `${startDateString} ${getTimeString(start)} ${start.format("[Uhr]")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if start and end are on the same day, dont show the day twice
|
||||||
|
if (startDateString === endDateString) {
|
||||||
|
return `${startDateString} ${getTimeString(start)} - ${getTimeString(
|
||||||
|
end
|
||||||
|
)} ${end.format("[Uhr]")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${startDateString} ${getTimeString(start)} - ${endDateString} ${getTimeString(
|
||||||
|
end
|
||||||
|
)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTimeString = (date?: Dayjs) => {
|
||||||
|
if (date) {
|
||||||
|
return `${date.format("H:mm")}`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDateString = (date?: Dayjs) => {
|
||||||
|
if (date) {
|
||||||
|
return `${date.format("D. MMMM YYYY")}`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getWeekday = (date: Dayjs) => {
|
||||||
|
if (date) {
|
||||||
|
return `${date.format("dd")}`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import type { ResultOf, TypedDocumentNode as DocumentNode, } from '@graphql-typed-document-node/core';
|
import type { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core';
|
||||||
|
import type { FragmentDefinitionNode } from 'graphql';
|
||||||
|
import type { Incremental } from './graphql';
|
||||||
|
|
||||||
|
|
||||||
export type FragmentType<TDocumentType extends DocumentNode<any, any>> = TDocumentType extends DocumentNode<
|
export type FragmentType<TDocumentType extends DocumentTypeDecoration<any, any>> = TDocumentType extends DocumentTypeDecoration<
|
||||||
infer TType,
|
infer TType,
|
||||||
any
|
any
|
||||||
>
|
>
|
||||||
? TType extends { ' $fragmentName'?: infer TKey }
|
? [TType] extends [{ ' $fragmentName'?: infer TKey }]
|
||||||
? TKey extends string
|
? TKey extends string
|
||||||
? { ' $fragmentRefs'?: { [key in TKey]: TType } }
|
? { ' $fragmentRefs'?: { [key in TKey]: TType } }
|
||||||
: never
|
: never
|
||||||
|
|
@ -14,35 +16,51 @@ export type FragmentType<TDocumentType extends DocumentNode<any, any>> = TDocume
|
||||||
|
|
||||||
// return non-nullable if `fragmentType` is non-nullable
|
// return non-nullable if `fragmentType` is non-nullable
|
||||||
export function useFragment<TType>(
|
export function useFragment<TType>(
|
||||||
_documentNode: DocumentNode<TType, any>,
|
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||||
fragmentType: FragmentType<DocumentNode<TType, any>>
|
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>
|
||||||
): TType;
|
): TType;
|
||||||
// return nullable if `fragmentType` is nullable
|
// return nullable if `fragmentType` is nullable
|
||||||
export function useFragment<TType>(
|
export function useFragment<TType>(
|
||||||
_documentNode: DocumentNode<TType, any>,
|
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||||
fragmentType: FragmentType<DocumentNode<TType, any>> | null | undefined
|
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null | undefined
|
||||||
): TType | null | undefined;
|
): TType | null | undefined;
|
||||||
// return array of non-nullable if `fragmentType` is array of non-nullable
|
// return array of non-nullable if `fragmentType` is array of non-nullable
|
||||||
export function useFragment<TType>(
|
export function useFragment<TType>(
|
||||||
_documentNode: DocumentNode<TType, any>,
|
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||||
fragmentType: ReadonlyArray<FragmentType<DocumentNode<TType, any>>>
|
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
|
||||||
): ReadonlyArray<TType>;
|
): ReadonlyArray<TType>;
|
||||||
// return array of nullable if `fragmentType` is array of nullable
|
// return array of nullable if `fragmentType` is array of nullable
|
||||||
export function useFragment<TType>(
|
export function useFragment<TType>(
|
||||||
_documentNode: DocumentNode<TType, any>,
|
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||||
fragmentType: ReadonlyArray<FragmentType<DocumentNode<TType, any>>> | null | undefined
|
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
|
||||||
): ReadonlyArray<TType> | null | undefined;
|
): ReadonlyArray<TType> | null | undefined;
|
||||||
export function useFragment<TType>(
|
export function useFragment<TType>(
|
||||||
_documentNode: DocumentNode<TType, any>,
|
_documentNode: DocumentTypeDecoration<TType, any>,
|
||||||
fragmentType: FragmentType<DocumentNode<TType, any>> | ReadonlyArray<FragmentType<DocumentNode<TType, any>>> | null | undefined
|
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
|
||||||
): TType | ReadonlyArray<TType> | null | undefined {
|
): TType | ReadonlyArray<TType> | null | undefined {
|
||||||
return fragmentType as any;
|
return fragmentType as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function makeFragmentData<
|
export function makeFragmentData<
|
||||||
F extends DocumentNode,
|
F extends DocumentTypeDecoration<any, any>,
|
||||||
FT extends ResultOf<F>
|
FT extends ResultOf<F>
|
||||||
>(data: FT, _fragment: F): FragmentType<F> {
|
>(data: FT, _fragment: F): FragmentType<F> {
|
||||||
return data as FragmentType<F>;
|
return data as FragmentType<F>;
|
||||||
}
|
}
|
||||||
|
export function isFragmentReady<TQuery, TFrag>(
|
||||||
|
queryNode: DocumentTypeDecoration<TQuery, any>,
|
||||||
|
fragmentNode: TypedDocumentNode<TFrag>,
|
||||||
|
data: FragmentType<TypedDocumentNode<Incremental<TFrag>, any>> | null | undefined
|
||||||
|
): data is FragmentType<typeof fragmentNode> {
|
||||||
|
const deferredFields = (queryNode as { __meta__?: { deferredFields: Record<string, (keyof TFrag)[]> } }).__meta__
|
||||||
|
?.deferredFields;
|
||||||
|
|
||||||
|
if (!deferredFields) return true;
|
||||||
|
|
||||||
|
const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined;
|
||||||
|
const fragName = fragDef?.name?.value;
|
||||||
|
|
||||||
|
const fields = (fragName && deferredFields[fragName]) || [];
|
||||||
|
return fields.length > 0 && fields.every(field => data && field in data);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-
|
||||||
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
|
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
|
||||||
* 3. It does not support dead code elimination, so it will add unused operations.
|
* 3. It does not support dead code elimination, so it will add unused operations.
|
||||||
*
|
*
|
||||||
* Therefore it is highly recommended to use the babel-plugin for production.
|
* Therefore it is highly recommended to use the babel or swc plugin for production.
|
||||||
*/
|
*/
|
||||||
const documents = {
|
const documents = {
|
||||||
"\n mutation SendFeedbackMutation($input: SendFeedbackInput!) {\n send_feedback(input: $input) {\n feedback_response {\n id\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
|
"\n mutation SendFeedbackMutation($input: SendFeedbackInput!) {\n send_feedback(input: $input) {\n feedback_response {\n id\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
|
||||||
|
|
@ -25,7 +25,7 @@ const documents = {
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* const query = gql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
|
* const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* The query argument is unknown!
|
* The query argument is unknown!
|
||||||
|
|
|
||||||
|
|
@ -5,33 +5,35 @@ export type InputMaybe<T> = Maybe<T>;
|
||||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
||||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
||||||
|
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
|
||||||
|
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
|
||||||
/** All built-in and custom scalars, mapped to their actual values */
|
/** All built-in and custom scalars, mapped to their actual values */
|
||||||
export type Scalars = {
|
export type Scalars = {
|
||||||
ID: string;
|
ID: { input: string; output: string; }
|
||||||
String: string;
|
String: { input: string; output: string; }
|
||||||
Boolean: boolean;
|
Boolean: { input: boolean; output: boolean; }
|
||||||
Int: number;
|
Int: { input: number; output: number; }
|
||||||
Float: number;
|
Float: { input: number; output: number; }
|
||||||
/**
|
/**
|
||||||
* The `DateTime` scalar type represents a DateTime
|
* The `DateTime` scalar type represents a DateTime
|
||||||
* value as specified by
|
* value as specified by
|
||||||
* [iso8601](https://en.wikipedia.org/wiki/ISO_8601).
|
* [iso8601](https://en.wikipedia.org/wiki/ISO_8601).
|
||||||
*/
|
*/
|
||||||
DateTime: any;
|
DateTime: { input: any; output: any; }
|
||||||
/**
|
/**
|
||||||
* The `GenericScalar` scalar type represents a generic
|
* The `GenericScalar` scalar type represents a generic
|
||||||
* GraphQL scalar value that could be:
|
* GraphQL scalar value that could be:
|
||||||
* String, Boolean, Int, Float, List or Object.
|
* String, Boolean, Int, Float, List or Object.
|
||||||
*/
|
*/
|
||||||
GenericScalar: any;
|
GenericScalar: { input: any; output: any; }
|
||||||
JSONStreamField: any;
|
JSONStreamField: { input: any; output: any; }
|
||||||
/**
|
/**
|
||||||
* Allows use of a JSON String for input / output from the GraphQL schema.
|
* Allows use of a JSON String for input / output from the GraphQL schema.
|
||||||
*
|
*
|
||||||
* Use of this type is *not recommended* as you lose the benefits of having a defined, static
|
* Use of this type is *not recommended* as you lose the benefits of having a defined, static
|
||||||
* schema (one of the key benefits of GraphQL).
|
* schema (one of the key benefits of GraphQL).
|
||||||
*/
|
*/
|
||||||
JSONString: any;
|
JSONString: { input: any; output: any; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/** An enumeration. */
|
/** An enumeration. */
|
||||||
|
|
@ -61,19 +63,19 @@ export type AssignmentCompletionMutation = {
|
||||||
|
|
||||||
export type AssignmentCompletionObjectType = {
|
export type AssignmentCompletionObjectType = {
|
||||||
__typename?: 'AssignmentCompletionObjectType';
|
__typename?: 'AssignmentCompletionObjectType';
|
||||||
additional_json_data: Scalars['JSONString'];
|
additional_json_data: Scalars['JSONString']['output'];
|
||||||
assignment: AssignmentObjectType;
|
assignment: AssignmentObjectType;
|
||||||
assignment_user: UserType;
|
assignment_user: UserType;
|
||||||
completion_data?: Maybe<Scalars['GenericScalar']>;
|
completion_data?: Maybe<Scalars['GenericScalar']['output']>;
|
||||||
completion_status: AssignmentAssignmentCompletionCompletionStatusChoices;
|
completion_status: AssignmentAssignmentCompletionCompletionStatusChoices;
|
||||||
created_at: Scalars['DateTime'];
|
created_at: Scalars['DateTime']['output'];
|
||||||
evaluation_grade?: Maybe<Scalars['Float']>;
|
evaluation_grade?: Maybe<Scalars['Float']['output']>;
|
||||||
evaluation_points?: Maybe<Scalars['Float']>;
|
evaluation_points?: Maybe<Scalars['Float']['output']>;
|
||||||
evaluation_submitted_at?: Maybe<Scalars['DateTime']>;
|
evaluation_submitted_at?: Maybe<Scalars['DateTime']['output']>;
|
||||||
evaluation_user?: Maybe<UserType>;
|
evaluation_user?: Maybe<UserType>;
|
||||||
id: Scalars['ID'];
|
id: Scalars['ID']['output'];
|
||||||
submitted_at?: Maybe<Scalars['DateTime']>;
|
submitted_at?: Maybe<Scalars['DateTime']['output']>;
|
||||||
updated_at: Scalars['DateTime'];
|
updated_at: Scalars['DateTime']['output'];
|
||||||
};
|
};
|
||||||
|
|
||||||
/** An enumeration. */
|
/** An enumeration. */
|
||||||
|
|
@ -86,24 +88,24 @@ export type AssignmentCompletionStatus =
|
||||||
export type AssignmentObjectType = CoursePageInterface & {
|
export type AssignmentObjectType = CoursePageInterface & {
|
||||||
__typename?: 'AssignmentObjectType';
|
__typename?: 'AssignmentObjectType';
|
||||||
assignment_type: AssignmentAssignmentAssignmentTypeChoices;
|
assignment_type: AssignmentAssignmentAssignmentTypeChoices;
|
||||||
content_type?: Maybe<Scalars['String']>;
|
content_type?: Maybe<Scalars['String']['output']>;
|
||||||
/** Zeitaufwand als Text */
|
/** Zeitaufwand als Text */
|
||||||
effort_required: Scalars['String'];
|
effort_required: Scalars['String']['output'];
|
||||||
/** Beschreibung der Bewertung */
|
/** Beschreibung der Bewertung */
|
||||||
evaluation_description: Scalars['String'];
|
evaluation_description: Scalars['String']['output'];
|
||||||
/** URL zum Beurteilungsinstrument */
|
/** URL zum Beurteilungsinstrument */
|
||||||
evaluation_document_url: Scalars['String'];
|
evaluation_document_url: Scalars['String']['output'];
|
||||||
evaluation_tasks?: Maybe<Scalars['JSONStreamField']>;
|
evaluation_tasks?: Maybe<Scalars['JSONStreamField']['output']>;
|
||||||
frontend_url?: Maybe<Scalars['String']>;
|
frontend_url?: Maybe<Scalars['String']['output']>;
|
||||||
id?: Maybe<Scalars['ID']>;
|
id?: Maybe<Scalars['ID']['output']>;
|
||||||
/** Erläuterung der Ausgangslage */
|
/** Erläuterung der Ausgangslage */
|
||||||
intro_text: Scalars['String'];
|
intro_text: Scalars['String']['output'];
|
||||||
live?: Maybe<Scalars['Boolean']>;
|
live?: Maybe<Scalars['Boolean']['output']>;
|
||||||
performance_objectives?: Maybe<Scalars['JSONStreamField']>;
|
performance_objectives?: Maybe<Scalars['JSONStreamField']['output']>;
|
||||||
slug?: Maybe<Scalars['String']>;
|
slug?: Maybe<Scalars['String']['output']>;
|
||||||
tasks?: Maybe<Scalars['JSONStreamField']>;
|
tasks?: Maybe<Scalars['JSONStreamField']['output']>;
|
||||||
title?: Maybe<Scalars['String']>;
|
title?: Maybe<Scalars['String']['output']>;
|
||||||
translation_key?: Maybe<Scalars['String']>;
|
translation_key?: Maybe<Scalars['String']['output']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** An enumeration. */
|
/** An enumeration. */
|
||||||
|
|
@ -116,69 +118,69 @@ export type CoreUserLanguageChoices =
|
||||||
| 'IT';
|
| 'IT';
|
||||||
|
|
||||||
export type CoursePageInterface = {
|
export type CoursePageInterface = {
|
||||||
content_type?: Maybe<Scalars['String']>;
|
content_type?: Maybe<Scalars['String']['output']>;
|
||||||
frontend_url?: Maybe<Scalars['String']>;
|
frontend_url?: Maybe<Scalars['String']['output']>;
|
||||||
id?: Maybe<Scalars['ID']>;
|
id?: Maybe<Scalars['ID']['output']>;
|
||||||
live?: Maybe<Scalars['Boolean']>;
|
live?: Maybe<Scalars['Boolean']['output']>;
|
||||||
slug?: Maybe<Scalars['String']>;
|
slug?: Maybe<Scalars['String']['output']>;
|
||||||
title?: Maybe<Scalars['String']>;
|
title?: Maybe<Scalars['String']['output']>;
|
||||||
translation_key?: Maybe<Scalars['String']>;
|
translation_key?: Maybe<Scalars['String']['output']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CourseType = {
|
export type CourseType = {
|
||||||
__typename?: 'CourseType';
|
__typename?: 'CourseType';
|
||||||
category_name: Scalars['String'];
|
category_name: Scalars['String']['output'];
|
||||||
id: Scalars['ID'];
|
id: Scalars['ID']['output'];
|
||||||
learning_path?: Maybe<LearningPathType>;
|
learning_path?: Maybe<LearningPathType>;
|
||||||
slug: Scalars['String'];
|
slug: Scalars['String']['output'];
|
||||||
title: Scalars['String'];
|
title: Scalars['String']['output'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ErrorType = {
|
export type ErrorType = {
|
||||||
__typename?: 'ErrorType';
|
__typename?: 'ErrorType';
|
||||||
field: Scalars['String'];
|
field: Scalars['String']['output'];
|
||||||
messages: Array<Scalars['String']>;
|
messages: Array<Scalars['String']['output']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FeedbackResponse = Node & {
|
export type FeedbackResponse = Node & {
|
||||||
__typename?: 'FeedbackResponse';
|
__typename?: 'FeedbackResponse';
|
||||||
created_at: Scalars['DateTime'];
|
created_at: Scalars['DateTime']['output'];
|
||||||
data?: Maybe<Scalars['GenericScalar']>;
|
data?: Maybe<Scalars['GenericScalar']['output']>;
|
||||||
/** The ID of the object */
|
/** The ID of the object */
|
||||||
id: Scalars['ID'];
|
id: Scalars['ID']['output'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LearningPathType = CoursePageInterface & {
|
export type LearningPathType = CoursePageInterface & {
|
||||||
__typename?: 'LearningPathType';
|
__typename?: 'LearningPathType';
|
||||||
content_type?: Maybe<Scalars['String']>;
|
content_type?: Maybe<Scalars['String']['output']>;
|
||||||
depth: Scalars['Int'];
|
depth: Scalars['Int']['output'];
|
||||||
draft_title: Scalars['String'];
|
draft_title: Scalars['String']['output'];
|
||||||
expire_at?: Maybe<Scalars['DateTime']>;
|
expire_at?: Maybe<Scalars['DateTime']['output']>;
|
||||||
expired: Scalars['Boolean'];
|
expired: Scalars['Boolean']['output'];
|
||||||
first_published_at?: Maybe<Scalars['DateTime']>;
|
first_published_at?: Maybe<Scalars['DateTime']['output']>;
|
||||||
frontend_url?: Maybe<Scalars['String']>;
|
frontend_url?: Maybe<Scalars['String']['output']>;
|
||||||
go_live_at?: Maybe<Scalars['DateTime']>;
|
go_live_at?: Maybe<Scalars['DateTime']['output']>;
|
||||||
has_unpublished_changes: Scalars['Boolean'];
|
has_unpublished_changes: Scalars['Boolean']['output'];
|
||||||
id?: Maybe<Scalars['ID']>;
|
id?: Maybe<Scalars['ID']['output']>;
|
||||||
last_published_at?: Maybe<Scalars['DateTime']>;
|
last_published_at?: Maybe<Scalars['DateTime']['output']>;
|
||||||
latest_revision_created_at?: Maybe<Scalars['DateTime']>;
|
latest_revision_created_at?: Maybe<Scalars['DateTime']['output']>;
|
||||||
live?: Maybe<Scalars['Boolean']>;
|
live?: Maybe<Scalars['Boolean']['output']>;
|
||||||
locked: Scalars['Boolean'];
|
locked: Scalars['Boolean']['output'];
|
||||||
locked_at?: Maybe<Scalars['DateTime']>;
|
locked_at?: Maybe<Scalars['DateTime']['output']>;
|
||||||
locked_by?: Maybe<UserType>;
|
locked_by?: Maybe<UserType>;
|
||||||
numchild: Scalars['Int'];
|
numchild: Scalars['Int']['output'];
|
||||||
owner?: Maybe<UserType>;
|
owner?: Maybe<UserType>;
|
||||||
path: Scalars['String'];
|
path: Scalars['String']['output'];
|
||||||
/** Die informative Beschreibung, dargestellt in Suchmaschinen-Ergebnissen unter der Überschrift. */
|
/** Die informative Beschreibung, dargestellt in Suchmaschinen-Ergebnissen unter der Überschrift. */
|
||||||
search_description: Scalars['String'];
|
search_description: Scalars['String']['output'];
|
||||||
/** Der Titel der Seite, dargestellt in Suchmaschinen-Ergebnissen als die verlinkte Überschrift. */
|
/** Der Titel der Seite, dargestellt in Suchmaschinen-Ergebnissen als die verlinkte Überschrift. */
|
||||||
seo_title: Scalars['String'];
|
seo_title: Scalars['String']['output'];
|
||||||
/** Ob ein Link zu dieser Seite in automatisch generierten Menüs auftaucht. */
|
/** Ob ein Link zu dieser Seite in automatisch generierten Menüs auftaucht. */
|
||||||
show_in_menus: Scalars['Boolean'];
|
show_in_menus: Scalars['Boolean']['output'];
|
||||||
slug?: Maybe<Scalars['String']>;
|
slug?: Maybe<Scalars['String']['output']>;
|
||||||
title?: Maybe<Scalars['String']>;
|
title?: Maybe<Scalars['String']['output']>;
|
||||||
translation_key?: Maybe<Scalars['String']>;
|
translation_key?: Maybe<Scalars['String']['output']>;
|
||||||
url_path: Scalars['String'];
|
url_path: Scalars['String']['output'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Mutation = {
|
export type Mutation = {
|
||||||
|
|
@ -194,19 +196,19 @@ export type MutationSendFeedbackArgs = {
|
||||||
|
|
||||||
|
|
||||||
export type MutationUpsertAssignmentCompletionArgs = {
|
export type MutationUpsertAssignmentCompletionArgs = {
|
||||||
assignment_id: Scalars['ID'];
|
assignment_id: Scalars['ID']['input'];
|
||||||
assignment_user_id?: InputMaybe<Scalars['ID']>;
|
assignment_user_id?: InputMaybe<Scalars['ID']['input']>;
|
||||||
completion_data_string?: InputMaybe<Scalars['String']>;
|
completion_data_string?: InputMaybe<Scalars['String']['input']>;
|
||||||
completion_status?: InputMaybe<AssignmentCompletionStatus>;
|
completion_status?: InputMaybe<AssignmentCompletionStatus>;
|
||||||
course_session_id: Scalars['ID'];
|
course_session_id: Scalars['ID']['input'];
|
||||||
evaluation_grade?: InputMaybe<Scalars['Float']>;
|
evaluation_grade?: InputMaybe<Scalars['Float']['input']>;
|
||||||
evaluation_points?: InputMaybe<Scalars['Float']>;
|
evaluation_points?: InputMaybe<Scalars['Float']['input']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** An object with an ID */
|
/** An object with an ID */
|
||||||
export type Node = {
|
export type Node = {
|
||||||
/** The ID of the object */
|
/** The ID of the object */
|
||||||
id: Scalars['ID'];
|
id: Scalars['ID']['output'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Query = {
|
export type Query = {
|
||||||
|
|
@ -218,32 +220,32 @@ export type Query = {
|
||||||
|
|
||||||
|
|
||||||
export type QueryAssignmentArgs = {
|
export type QueryAssignmentArgs = {
|
||||||
id?: InputMaybe<Scalars['ID']>;
|
id?: InputMaybe<Scalars['ID']['input']>;
|
||||||
slug?: InputMaybe<Scalars['String']>;
|
slug?: InputMaybe<Scalars['String']['input']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type QueryAssignmentCompletionArgs = {
|
export type QueryAssignmentCompletionArgs = {
|
||||||
assignment_id: Scalars['ID'];
|
assignment_id: Scalars['ID']['input'];
|
||||||
assignment_user_id?: InputMaybe<Scalars['ID']>;
|
assignment_user_id?: InputMaybe<Scalars['ID']['input']>;
|
||||||
course_session_id: Scalars['ID'];
|
course_session_id: Scalars['ID']['input'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type QueryCourseArgs = {
|
export type QueryCourseArgs = {
|
||||||
id?: InputMaybe<Scalars['Int']>;
|
id?: InputMaybe<Scalars['Int']['input']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SendFeedbackInput = {
|
export type SendFeedbackInput = {
|
||||||
clientMutationId?: InputMaybe<Scalars['String']>;
|
clientMutationId?: InputMaybe<Scalars['String']['input']>;
|
||||||
course_session: Scalars['Int'];
|
course_session: Scalars['Int']['input'];
|
||||||
data?: InputMaybe<Scalars['GenericScalar']>;
|
data?: InputMaybe<Scalars['GenericScalar']['input']>;
|
||||||
page: Scalars['String'];
|
page: Scalars['String']['input'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SendFeedbackPayload = {
|
export type SendFeedbackPayload = {
|
||||||
__typename?: 'SendFeedbackPayload';
|
__typename?: 'SendFeedbackPayload';
|
||||||
clientMutationId?: Maybe<Scalars['String']>;
|
clientMutationId?: Maybe<Scalars['String']['output']>;
|
||||||
/** May contain more than one error for same field. */
|
/** May contain more than one error for same field. */
|
||||||
errors?: Maybe<Array<Maybe<ErrorType>>>;
|
errors?: Maybe<Array<Maybe<ErrorType>>>;
|
||||||
feedback_response?: Maybe<FeedbackResponse>;
|
feedback_response?: Maybe<FeedbackResponse>;
|
||||||
|
|
@ -251,14 +253,14 @@ export type SendFeedbackPayload = {
|
||||||
|
|
||||||
export type UserType = {
|
export type UserType = {
|
||||||
__typename?: 'UserType';
|
__typename?: 'UserType';
|
||||||
avatar_url: Scalars['String'];
|
avatar_url: Scalars['String']['output'];
|
||||||
email: Scalars['String'];
|
email: Scalars['String']['output'];
|
||||||
first_name: Scalars['String'];
|
first_name: Scalars['String']['output'];
|
||||||
id: Scalars['ID'];
|
id: Scalars['ID']['output'];
|
||||||
language: CoreUserLanguageChoices;
|
language: CoreUserLanguageChoices;
|
||||||
last_name: Scalars['String'];
|
last_name: Scalars['String']['output'];
|
||||||
/** Erforderlich. 150 Zeichen oder weniger. Nur Buchstaben, Ziffern und @/./+/-/_. */
|
/** Erforderlich. 150 Zeichen oder weniger. Nur Buchstaben, Ziffern und @/./+/-/_. */
|
||||||
username: Scalars['String'];
|
username: Scalars['String']['output'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SendFeedbackMutationMutationVariables = Exact<{
|
export type SendFeedbackMutationMutationVariables = Exact<{
|
||||||
|
|
@ -269,29 +271,29 @@ export type SendFeedbackMutationMutationVariables = Exact<{
|
||||||
export type SendFeedbackMutationMutation = { __typename?: 'Mutation', send_feedback?: { __typename?: 'SendFeedbackPayload', feedback_response?: { __typename?: 'FeedbackResponse', id: string } | null, errors?: Array<{ __typename?: 'ErrorType', field: string, messages: Array<string> } | null> | null } | null };
|
export type SendFeedbackMutationMutation = { __typename?: 'Mutation', send_feedback?: { __typename?: 'SendFeedbackPayload', feedback_response?: { __typename?: 'FeedbackResponse', id: string } | null, errors?: Array<{ __typename?: 'ErrorType', field: string, messages: Array<string> } | null> | null } | null };
|
||||||
|
|
||||||
export type UpsertAssignmentCompletionMutationVariables = Exact<{
|
export type UpsertAssignmentCompletionMutationVariables = Exact<{
|
||||||
assignmentId: Scalars['ID'];
|
assignmentId: Scalars['ID']['input'];
|
||||||
courseSessionId: Scalars['ID'];
|
courseSessionId: Scalars['ID']['input'];
|
||||||
assignmentUserId?: InputMaybe<Scalars['ID']>;
|
assignmentUserId?: InputMaybe<Scalars['ID']['input']>;
|
||||||
completionStatus: AssignmentCompletionStatus;
|
completionStatus: AssignmentCompletionStatus;
|
||||||
completionDataString: Scalars['String'];
|
completionDataString: Scalars['String']['input'];
|
||||||
evaluationGrade?: InputMaybe<Scalars['Float']>;
|
evaluationGrade?: InputMaybe<Scalars['Float']['input']>;
|
||||||
evaluationPoints?: InputMaybe<Scalars['Float']>;
|
evaluationPoints?: InputMaybe<Scalars['Float']['input']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type UpsertAssignmentCompletionMutation = { __typename?: 'Mutation', upsert_assignment_completion?: { __typename?: 'AssignmentCompletionMutation', assignment_completion?: { __typename?: 'AssignmentCompletionObjectType', id: string, completion_status: AssignmentAssignmentCompletionCompletionStatusChoices, submitted_at?: any | null, evaluation_submitted_at?: any | null, evaluation_grade?: number | null, evaluation_points?: number | null, completion_data?: any | null } | null } | null };
|
export type UpsertAssignmentCompletionMutation = { __typename?: 'Mutation', upsert_assignment_completion?: { __typename?: 'AssignmentCompletionMutation', assignment_completion?: { __typename?: 'AssignmentCompletionObjectType', id: string, completion_status: AssignmentAssignmentCompletionCompletionStatusChoices, submitted_at?: any | null, evaluation_submitted_at?: any | null, evaluation_grade?: number | null, evaluation_points?: number | null, completion_data?: any | null } | null } | null };
|
||||||
|
|
||||||
export type AssignmentCompletionQueryQueryVariables = Exact<{
|
export type AssignmentCompletionQueryQueryVariables = Exact<{
|
||||||
assignmentId: Scalars['ID'];
|
assignmentId: Scalars['ID']['input'];
|
||||||
courseSessionId: Scalars['ID'];
|
courseSessionId: Scalars['ID']['input'];
|
||||||
assignmentUserId?: InputMaybe<Scalars['ID']>;
|
assignmentUserId?: InputMaybe<Scalars['ID']['input']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type AssignmentCompletionQueryQuery = { __typename?: 'Query', assignment?: { __typename?: 'AssignmentObjectType', assignment_type: AssignmentAssignmentAssignmentTypeChoices, content_type?: string | null, effort_required: string, evaluation_description: string, evaluation_document_url: string, evaluation_tasks?: any | null, id?: string | null, intro_text: string, performance_objectives?: any | null, slug?: string | null, tasks?: any | null, title?: string | null, translation_key?: string | null } | null, assignment_completion?: { __typename?: 'AssignmentCompletionObjectType', id: string, completion_status: AssignmentAssignmentCompletionCompletionStatusChoices, submitted_at?: any | null, evaluation_submitted_at?: any | null, evaluation_grade?: number | null, evaluation_points?: number | null, completion_data?: any | null, evaluation_user?: { __typename?: 'UserType', id: string } | null, assignment_user: { __typename?: 'UserType', id: string } } | null };
|
export type AssignmentCompletionQueryQuery = { __typename?: 'Query', assignment?: { __typename?: 'AssignmentObjectType', assignment_type: AssignmentAssignmentAssignmentTypeChoices, content_type?: string | null, effort_required: string, evaluation_description: string, evaluation_document_url: string, evaluation_tasks?: any | null, id?: string | null, intro_text: string, performance_objectives?: any | null, slug?: string | null, tasks?: any | null, title?: string | null, translation_key?: string | null } | null, assignment_completion?: { __typename?: 'AssignmentCompletionObjectType', id: string, completion_status: AssignmentAssignmentCompletionCompletionStatusChoices, submitted_at?: any | null, evaluation_submitted_at?: any | null, evaluation_grade?: number | null, evaluation_points?: number | null, completion_data?: any | null, evaluation_user?: { __typename?: 'UserType', id: string } | null, assignment_user: { __typename?: 'UserType', id: string } } | null };
|
||||||
|
|
||||||
export type CourseQueryQueryVariables = Exact<{
|
export type CourseQueryQueryVariables = Exact<{
|
||||||
courseId: Scalars['Int'];
|
courseId: Scalars['Int']['input'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
"assignmentSubmitted": "Du hast deine Ergebnisse erfolgreich abgegeben.",
|
"assignmentSubmitted": "Du hast deine Ergebnisse erfolgreich abgegeben.",
|
||||||
"confirmSubmitPerson": "Hiermit bestätige ich, dass die folgende Person meine Ergebnisse bewerten soll.",
|
"confirmSubmitPerson": "Hiermit bestätige ich, dass die folgende Person meine Ergebnisse bewerten soll.",
|
||||||
"confirmSubmitResults": "Hiermit bestätige ich, dass ich die Zusammenfassung meiner Ergebnisse überprüft habe und so abgeben will.",
|
"confirmSubmitResults": "Hiermit bestätige ich, dass ich die Zusammenfassung meiner Ergebnisse überprüft habe und so abgeben will.",
|
||||||
"dueDateIntroduction": "Reiche deine Ergebnisse pünktlich ein bis am {{date}} um {{time}} Uhr ein.",
|
"dueDateIntroduction": "Reiche deine Ergebnisse pünktlich ein bis am: ",
|
||||||
"dueDateNotSet": "Keine Abgabedaten wurden erfasst für diese Durchführung",
|
"dueDateNotSet": "Keine Abgabedaten wurden erfasst für diese Durchführung",
|
||||||
"dueDateSubmission": "Einreichungstermin: {{date}}",
|
"dueDateSubmission": "Einreichungstermin:",
|
||||||
"dueDateTitle": "Abgabetermin",
|
"dueDateTitle": "Abgabetermin",
|
||||||
"edit": "Bearbeiten",
|
"edit": "Bearbeiten",
|
||||||
"effortTitle": "Zeitaufwand",
|
"effortTitle": "Zeitaufwand",
|
||||||
|
|
@ -85,11 +85,14 @@
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"courses": "Lehrgang",
|
"courses": "Lehrgang",
|
||||||
|
"dueDatesTitle": "Termine",
|
||||||
"nocourses": "Du wurdest noch keinem Lehrgang zugewiesen.",
|
"nocourses": "Du wurdest noch keinem Lehrgang zugewiesen.",
|
||||||
"welcome": "Willkommen, {{name}}"
|
"welcome": "Willkommen, {{name}}"
|
||||||
},
|
},
|
||||||
"dueDates": {
|
"dueDates": {
|
||||||
"nextDueDates": "Nächste Termine"
|
"nextDueDates": "Nächste Termine",
|
||||||
|
"noDueDatesAvailable": "Keine Termine vorhanden",
|
||||||
|
"showAllDueDates": "Alle Termine anzeigen"
|
||||||
},
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"answers": "Antworten",
|
"answers": "Antworten",
|
||||||
|
|
@ -185,6 +188,7 @@
|
||||||
"learningPathPage": {
|
"learningPathPage": {
|
||||||
"currentCircle": "Aktueller Circle",
|
"currentCircle": "Aktueller Circle",
|
||||||
"listView": "Listenansicht",
|
"listView": "Listenansicht",
|
||||||
|
"nextDueDates": "Nächste Termine",
|
||||||
"nextStep": "Nächster Schritt",
|
"nextStep": "Nächster Schritt",
|
||||||
"pathView": "Pfadansicht",
|
"pathView": "Pfadansicht",
|
||||||
"progressText": "Du hast {{ inProgressCount }} von {{ allCount }} Circles bearbeitet",
|
"progressText": "Du hast {{ inProgressCount }} von {{ allCount }} Circles bearbeitet",
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
"assignmentSubmitted": "Tes résultats ont bien été transmis.",
|
"assignmentSubmitted": "Tes résultats ont bien été transmis.",
|
||||||
"confirmSubmitPerson": "Par la présente, je confirme que la personne suivante doit évaluer mes résultats.",
|
"confirmSubmitPerson": "Par la présente, je confirme que la personne suivante doit évaluer mes résultats.",
|
||||||
"confirmSubmitResults": "Par la présente, je confirme que j’ai vérifié la synthèse de mes résultats et que je souhaite la remettre telle quelle.",
|
"confirmSubmitResults": "Par la présente, je confirme que j’ai vérifié la synthèse de mes résultats et que je souhaite la remettre telle quelle.",
|
||||||
"dueDateIntroduction": "Envoie tes résultats dans les délais avant le {{date}} à {{time}} heures.",
|
"dueDateIntroduction": "Envoie tes résultats dans les délais avant le:",
|
||||||
"dueDateNotSet": "Aucune date de remise n’a été spécifiée pour cette opération.",
|
"dueDateNotSet": "Aucune date de remise n’a été spécifiée pour cette opération.",
|
||||||
"dueDateSubmission": "Date de clôture : {{date}}",
|
"dueDateSubmission": "Date de clôture: ",
|
||||||
"dueDateTitle": "Date de remise",
|
"dueDateTitle": "Date de remise",
|
||||||
"edit": "Traiter",
|
"edit": "Traiter",
|
||||||
"effortTitle": "Temps nécessaire",
|
"effortTitle": "Temps nécessaire",
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
"assignmentSubmitted": "I tuoi risultati sono stati consegnati con successo.",
|
"assignmentSubmitted": "I tuoi risultati sono stati consegnati con successo.",
|
||||||
"confirmSubmitPerson": "Confermo che i miei risultati dovranno essere valutati dalla seguente persona.",
|
"confirmSubmitPerson": "Confermo che i miei risultati dovranno essere valutati dalla seguente persona.",
|
||||||
"confirmSubmitResults": "Confermo di aver controllato il riepilogo dei miei risultati e di volerli consegnare.",
|
"confirmSubmitResults": "Confermo di aver controllato il riepilogo dei miei risultati e di volerli consegnare.",
|
||||||
"dueDateIntroduction": "Presenta i tuoi risultati entro il {{date}} alle {{time}}.",
|
"dueDateIntroduction": "Presenta i tuoi risultati entro il:",
|
||||||
"dueDateNotSet": "Non sono stati registrati dati di consegna per questo svolgimento",
|
"dueDateNotSet": "Non sono stati registrati dati di consegna per questo svolgimento",
|
||||||
"dueDateSubmission": "Termine di presentazione: {{date}}",
|
"dueDateSubmission": "Termine di presentazione: ",
|
||||||
"dueDateTitle": "Termine di consegna",
|
"dueDateTitle": "Termine di consegna",
|
||||||
"edit": "Modificare",
|
"edit": "Modificare",
|
||||||
"effortTitle": "Tempo richiesto",
|
"effortTitle": "Tempo richiesto",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import DueDatesList from "@/components/dueDates/DueDatesList.vue";
|
||||||
import LearningPathDiagramSmall from "@/components/learningPath/LearningPathDiagramSmall.vue";
|
import LearningPathDiagramSmall from "@/components/learningPath/LearningPathDiagramSmall.vue";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
|
|
@ -15,6 +16,7 @@ onMounted(async () => {
|
||||||
log.debug("DashboardPage mounted");
|
log.debug("DashboardPage mounted");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const allDueDates = courseSessionsStore.allDueDates();
|
||||||
const getNextStepLink = (courseSession: CourseSession) => {
|
const getNextStepLink = (courseSession: CourseSession) => {
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
if (courseSessionsStore.hasCockpit(courseSession)) {
|
if (courseSessionsStore.hasCockpit(courseSession)) {
|
||||||
|
|
@ -69,22 +71,13 @@ const getNextStepLink = (courseSession: CourseSession) => {
|
||||||
<p>{{ $t("dashboard.nocourses") }}</p>
|
<p>{{ $t("dashboard.nocourses") }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="mb-6">Termine</h3>
|
<h3 class="mb-6">{{ $t("dashboard.dueDatesTitle") }}</h3>
|
||||||
<ul class="bg-white p-4">
|
<DueDatesList
|
||||||
<li class="flex flex-row py-4">
|
class="bg-white p-6"
|
||||||
{{ $t("Zur Zeit sind keine Termine vorhanden") }}
|
:due-dates="allDueDates"
|
||||||
</li>
|
:max-count="10"
|
||||||
<!-- li class="flex flex-row border-b py-4">
|
:show-top-border="false"
|
||||||
<p class="text-bold w-60">Austausch mit Trainer</p>
|
></DueDatesList>
|
||||||
<p class="grow">Fr, 24. November 2022, 11 Uhr</p>
|
|
||||||
<p class="underline">Details anschauen</p>
|
|
||||||
</li>
|
|
||||||
<li class="flex flex-row py-4">
|
|
||||||
<p class="text-bold w-60">Vernetzen - Live Session</p>
|
|
||||||
<p class="grow">Di, 4. Dezember 2022, 10.30 Uhr</p>
|
|
||||||
<p class="underline">Details anschauen</p>
|
|
||||||
</li -->
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,15 @@ function log(data: any) {
|
||||||
ls-end
|
ls-end
|
||||||
<it-icon-ls-end />
|
<it-icon-ls-end />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="inline-flex flex-col">
|
||||||
|
calendar
|
||||||
|
<it-icon-calendar />
|
||||||
|
</div>
|
||||||
|
<div class="inline-flex flex-col">
|
||||||
|
calendar-light
|
||||||
|
<it-icon-calenda-light />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-8 mt-8 flex flex-col flex-wrap gap-4 lg:flex-row">
|
<div class="mb-8 mt-8 flex flex-col flex-wrap gap-4 lg:flex-row">
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentP
|
||||||
import type {
|
import type {
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentCompletion,
|
AssignmentCompletion,
|
||||||
CourseSessionAssignmentDetails,
|
CourseSessionAssignment,
|
||||||
CourseSessionUser,
|
CourseSessionUser,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import { useQuery } from "@urql/vue";
|
import { useQuery } from "@urql/vue";
|
||||||
|
|
@ -23,12 +23,12 @@ const props = defineProps<{
|
||||||
log.debug("AssignmentEvaluationPage created", props.assignmentId, props.userId);
|
log.debug("AssignmentEvaluationPage created", props.assignmentId, props.userId);
|
||||||
|
|
||||||
interface StateInterface {
|
interface StateInterface {
|
||||||
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
|
courseSessionAssignment: CourseSessionAssignment | undefined;
|
||||||
assignmentUser: CourseSessionUser | undefined;
|
assignmentUser: CourseSessionUser | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: StateInterface = reactive({
|
const state: StateInterface = reactive({
|
||||||
courseSessionAssignmentDetails: undefined,
|
courseSessionAssignment: undefined,
|
||||||
assignmentUser: undefined,
|
assignmentUser: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ function editTask(task: AssignmentEvaluationTask) {
|
||||||
const assignmentDetail = computed(() => findAssignmentDetail(props.assignment.id));
|
const assignmentDetail = computed(() => findAssignmentDetail(props.assignment.id));
|
||||||
|
|
||||||
const dueDate = computed(() =>
|
const dueDate = computed(() =>
|
||||||
dayjs(assignmentDetail.value?.evaluationDeadlineDateTimeUtc)
|
dayjs(assignmentDetail.value?.evaluation_deadline_start)
|
||||||
);
|
);
|
||||||
|
|
||||||
const inEvaluationTask = computed(
|
const inEvaluationTask = computed(
|
||||||
|
|
|
||||||
|
|
@ -56,12 +56,12 @@ const assignmentDetail = computed(() =>
|
||||||
<div v-if="assignmentDetail">
|
<div v-if="assignmentDetail">
|
||||||
<span>
|
<span>
|
||||||
Abgabetermin:
|
Abgabetermin:
|
||||||
{{ dayjs(assignmentDetail.submissionDeadlineDateTimeUtc).format("DD.MM.YYYY") }}
|
{{ dayjs(assignmentDetail.submission_deadline_start).format("DD.MM.YYYY") }}
|
||||||
</span>
|
</span>
|
||||||
-
|
-
|
||||||
<span>
|
<span>
|
||||||
Freigabetermin:
|
Freigabetermin:
|
||||||
{{ dayjs(assignmentDetail.evaluationDeadlineDateTimeUtc).format("DD.MM.YYYY") }}
|
{{ dayjs(assignmentDetail.evaluation_deadline_start).format("DD.MM.YYYY") }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import DateEmbedding from "@/components/dueDates/DateEmbedding.vue";
|
||||||
import type { Assignment } from "@/types";
|
import type { Assignment } from "@/types";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import type { Dayjs } from "dayjs";
|
import type { Dayjs } from "dayjs";
|
||||||
|
import log from "loglevel";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assignment: Assignment;
|
assignment: Assignment;
|
||||||
|
|
@ -12,6 +14,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
dueDate: undefined,
|
dueDate: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
log.debug("AssignmentIntroductionView created", props.assignment, props.dueDate);
|
||||||
|
|
||||||
|
// TODO: Test if submission deadline is set correctly, and evaluation_deadline is set.
|
||||||
|
|
||||||
const step = useRouteQuery("step");
|
const step = useRouteQuery("step");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -37,12 +43,8 @@ const step = useRouteQuery("step");
|
||||||
|
|
||||||
<h3 class="mb-4 mt-8">{{ $t("assignment.dueDateTitle") }}</h3>
|
<h3 class="mb-4 mt-8">{{ $t("assignment.dueDateTitle") }}</h3>
|
||||||
<p v-if="props.dueDate" class="text-large">
|
<p v-if="props.dueDate" class="text-large">
|
||||||
{{
|
{{ $t("assignment.dueDateIntroduction") }}
|
||||||
$t("assignment.dueDateIntroduction", {
|
<DateEmbedding :single-date="dueDate"></DateEmbedding>
|
||||||
date: dueDate!.format("DD.MM.YYYY"),
|
|
||||||
time: dueDate!.format("HH:mm"),
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-large">
|
<p v-else class="text-large">
|
||||||
{{ $t("assignment.dueDateNotSet") }}
|
{{ $t("assignment.dueDateNotSet") }}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import DateEmbedding from "@/components/dueDates/DateEmbedding.vue";
|
||||||
import ItButton from "@/components/ui/ItButton.vue";
|
import ItButton from "@/components/ui/ItButton.vue";
|
||||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||||
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
||||||
|
|
@ -119,7 +120,8 @@ const onSubmit = async () => {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="pt-6">
|
<p class="pt-6">
|
||||||
{{ $t("assignment.dueDateSubmission", { date: dueDate.format("DD.MM.YYYY") }) }}
|
{{ $t("assignment.dueDateSubmission") }}
|
||||||
|
<DateEmbedding :single-date="dueDate"></DateEmbedding>
|
||||||
</p>
|
</p>
|
||||||
<ItButton
|
<ItButton
|
||||||
class="mt-6"
|
class="mt-6"
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import type {
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentCompletion,
|
AssignmentCompletion,
|
||||||
AssignmentTask,
|
AssignmentTask,
|
||||||
CourseSessionAssignmentDetails,
|
CourseSessionAssignment,
|
||||||
CourseSessionUser,
|
CourseSessionUser,
|
||||||
LearningContentAssignment,
|
LearningContentAssignment,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
|
|
@ -29,11 +29,11 @@ const courseSession = useCurrentCourseSession();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
|
courseSessionAssignment: CourseSessionAssignment | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: State = reactive({
|
const state: State = reactive({
|
||||||
courseSessionAssignmentDetails: undefined,
|
courseSessionAssignment: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -80,7 +80,7 @@ onMounted(async () => {
|
||||||
props.learningContent
|
props.learningContent
|
||||||
);
|
);
|
||||||
|
|
||||||
state.courseSessionAssignmentDetails = useCourseSessionsStore().findAssignmentDetails(
|
state.courseSessionAssignment = useCourseSessionsStore().findCourseSessionAssignment(
|
||||||
props.learningContent.id
|
props.learningContent.id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -123,7 +123,7 @@ const showPreviousButton = computed(() => stepIndex.value != 0);
|
||||||
const showNextButton = computed(() => stepIndex.value + 1 < numPages.value);
|
const showNextButton = computed(() => stepIndex.value + 1 < numPages.value);
|
||||||
const showExitButton = computed(() => numPages.value === stepIndex.value + 1);
|
const showExitButton = computed(() => numPages.value === stepIndex.value + 1);
|
||||||
const dueDate = computed(() =>
|
const dueDate = computed(() =>
|
||||||
dayjs(state.courseSessionAssignmentDetails?.submissionDeadlineDateTimeUtc)
|
dayjs(state.courseSessionAssignment?.submission_deadline_start)
|
||||||
);
|
);
|
||||||
const currentTask = computed(() => {
|
const currentTask = computed(() => {
|
||||||
if (stepIndex.value > 0 && stepIndex.value <= numTasks.value) {
|
if (stepIndex.value > 0 && stepIndex.value <= numTasks.value) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mb-12 grid grid-cols-icon-card gap-x-4 grid-areas-icon-card">
|
<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" />
|
<it-icon-calendar-light class="w-[60px] grid-in-icon" />
|
||||||
<h2 class="text-large font-bold grid-in-title">Datum</h2>
|
<h2 class="text-large font-bold grid-in-title">Datum</h2>
|
||||||
<p class="grid-in-value">{{ start }} - {{ end }}</p>
|
<p class="grid-in-value">{{ formatDate(start, end) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-12 grid grid-cols-icon-card gap-x-4 grid-areas-icon-card">
|
<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" />
|
<it-icon-location class="w-[60px] grid-in-icon" />
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { formatDate } from "@/components/dueDates/dueDatesUtils";
|
||||||
import type { CourseSessionAttendanceCourse } from "@/types";
|
import type { CourseSessionAttendanceCourse } from "@/types";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import LocalizedFormat from "dayjs/plugin/localizedFormat";
|
import LocalizedFormat from "dayjs/plugin/localizedFormat";
|
||||||
|
|
@ -32,9 +33,8 @@ export interface Props {
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
dayjs.extend(LocalizedFormat);
|
dayjs.extend(LocalizedFormat);
|
||||||
const format = "LLLL";
|
const start = computed(() => dayjs(props.attendanceCourse.start));
|
||||||
const start = computed(() => dayjs(props.attendanceCourse.start).format(format));
|
const end = computed(() => dayjs(props.attendanceCourse.end));
|
||||||
const end = computed(() => dayjs(props.attendanceCourse.end).format(format));
|
|
||||||
const location = computed(() => props.attendanceCourse.location);
|
const location = computed(() => props.attendanceCourse.location);
|
||||||
const trainer = computed(() => props.attendanceCourse.trainer);
|
const trainer = computed(() => props.attendanceCourse.trainer);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<template>
|
|
||||||
<p class="mb-4 font-bold">{{ $t("dueDates.nextDueDates") }}:</p>
|
|
||||||
<!-- ul>
|
|
||||||
<li class="border-b border-t py-3">
|
|
||||||
<p class="pr-12">24. November 2022, 11 Uhr - Austausch mit Trainer</p>
|
|
||||||
</li>
|
|
||||||
<li class="border-b py-3">
|
|
||||||
<p class="pr-12">4. Dezember 2022, 10.30 Uhr - Vernetzen - Live Session</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p class="pt-2 underline">Alle Termine anzeigen</p -->
|
|
||||||
<p class="min-h-[150px] pt-2">{{ $t("Zur Zeit sind keine Termine vorhanden") }}</p>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import * as log from "loglevel";
|
|
||||||
import { computed, onMounted } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -17,7 +16,6 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
log.debug("LearningPathCircle mounted");
|
|
||||||
render();
|
render();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LearningPathAppointmentsMock from "@/pages/learningPath/learningPathPage/LearningPathAppointmentsMock.vue";
|
import DueDatesShortList from "@/components/dueDates/DueDatesShortList.vue";
|
||||||
import LearningPathListView from "@/pages/learningPath/learningPathPage/LearningPathListView.vue";
|
import LearningPathListView from "@/pages/learningPath/learningPathPage/LearningPathListView.vue";
|
||||||
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
import LearningPathPathView from "@/pages/learningPath/learningPathPage/LearningPathPathView.vue";
|
||||||
import CircleProgress from "@/pages/learningPath/learningPathPage/LearningPathProgress.vue";
|
import CircleProgress from "@/pages/learningPath/learningPathPage/LearningPathProgress.vue";
|
||||||
|
|
@ -74,7 +74,7 @@ const changeViewType = (viewType: ViewType) => {
|
||||||
<!-- Top -->
|
<!-- Top -->
|
||||||
<div class="flex flex-row justify-between space-x-8 bg-gray-200 p-6 sm:p-12">
|
<div class="flex flex-row justify-between space-x-8 bg-gray-200 p-6 sm:p-12">
|
||||||
<!-- Left -->
|
<!-- Left -->
|
||||||
<div class="flex flex-col justify-between">
|
<div class="flex w-1/2 flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="font-bold">
|
<p class="font-bold">
|
||||||
{{ $t("learningPathPage.welcomeBack") }}
|
{{ $t("learningPathPage.welcomeBack") }}
|
||||||
|
|
@ -91,8 +91,11 @@ const changeViewType = (viewType: ViewType) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right -->
|
<!-- Right -->
|
||||||
<div v-if="!useMobileLayout" class="max-w-md">
|
<div v-if="!useMobileLayout" class="flex-grow">
|
||||||
<LearningPathAppointmentsMock></LearningPathAppointmentsMock>
|
<div class="text-bold pb-3">
|
||||||
|
{{ $t("learningPathPage.nextDueDates") }}
|
||||||
|
</div>
|
||||||
|
<DueDatesShortList :max-count="2" :show-top-border="true"></DueDatesShortList>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ export function findAssignmentDetail(assignmentId: number) {
|
||||||
(lc) => lc.assignmentId === assignmentId
|
(lc) => lc.assignmentId === assignmentId
|
||||||
);
|
);
|
||||||
|
|
||||||
return courseSessionsStore.findAssignmentDetails(learningContent?.id);
|
return courseSessionsStore.findCourseSessionAssignment(learningContent?.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maxAssignmentPoints(assignment: Assignment) {
|
export function maxAssignmentPoints(assignment: Assignment) {
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ import { deleteCircleDocument } from "@/services/files";
|
||||||
import type {
|
import type {
|
||||||
CircleDocument,
|
CircleDocument,
|
||||||
CourseSession,
|
CourseSession,
|
||||||
CourseSessionAssignmentDetails,
|
CourseSessionAssignment,
|
||||||
CourseSessionAttendanceCourse,
|
CourseSessionAttendanceCourse,
|
||||||
CourseSessionUser,
|
CourseSessionUser,
|
||||||
|
DueDate,
|
||||||
ExpertSessionUser,
|
ExpertSessionUser,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import eventBus from "@/utils/eventBus";
|
import eventBus from "@/utils/eventBus";
|
||||||
import { useLocalStorage } from "@vueuse/core";
|
import { useLocalStorage } from "@vueuse/core";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import uniqBy from "lodash/uniqBy";
|
import uniqBy from "lodash/uniqBy";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
@ -25,7 +27,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
|
|
||||||
async function loadCourseSessionsData(reload = false) {
|
async function loadCourseSessionsData(reload = false) {
|
||||||
log.debug("loadCourseSessionsData called");
|
log.debug("loadCourseSessionsData called");
|
||||||
|
|
||||||
allCourseSessions.value = await itGetCached(`/api/course/sessions/`, {
|
allCourseSessions.value = await itGetCached(`/api/course/sessions/`, {
|
||||||
reload: reload,
|
reload: reload,
|
||||||
});
|
});
|
||||||
|
|
@ -39,6 +40,10 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
reload: reload,
|
reload: reload,
|
||||||
})) as CourseSessionUser[];
|
})) as CourseSessionUser[];
|
||||||
cs.users = users;
|
cs.users = users;
|
||||||
|
cs.due_dates.forEach((dueDate) => {
|
||||||
|
dueDate.start = dayjs(dueDate.start);
|
||||||
|
dueDate.end = dayjs(dueDate.end);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -190,6 +195,16 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
currentCourseSession.value?.documents.push(document);
|
currentCourseSession.value?.documents.push(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function allDueDates() {
|
||||||
|
const allDueDatesReturn: DueDate[] = [];
|
||||||
|
|
||||||
|
allCourseSessions.value?.forEach((cs) => {
|
||||||
|
allDueDatesReturn.push(...cs.due_dates);
|
||||||
|
});
|
||||||
|
allDueDatesReturn.sort((a, b) => dayjs(a.end).diff(dayjs(b.end)));
|
||||||
|
return allDueDatesReturn;
|
||||||
|
}
|
||||||
|
|
||||||
async function startUpload() {
|
async function startUpload() {
|
||||||
log.debug("loadCourseSessionsData called");
|
log.debug("loadCourseSessionsData called");
|
||||||
allCourseSessions.value = await itPost(`/api/core/file/start`, {
|
allCourseSessions.value = await itPost(`/api/core/file/start`, {
|
||||||
|
|
@ -215,17 +230,17 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
): CourseSessionAttendanceCourse | undefined {
|
): CourseSessionAttendanceCourse | undefined {
|
||||||
if (currentCourseSession.value) {
|
if (currentCourseSession.value) {
|
||||||
return currentCourseSession.value.attendance_courses.find(
|
return currentCourseSession.value.attendance_courses.find(
|
||||||
(attendanceCourse) => attendanceCourse.learningContentId === contentId
|
(attendanceCourse) => attendanceCourse.learning_content_id === contentId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findAssignmentDetails(
|
function findCourseSessionAssignment(
|
||||||
contentId?: number
|
contentId?: number
|
||||||
): CourseSessionAssignmentDetails | undefined {
|
): CourseSessionAssignment | undefined {
|
||||||
if (contentId && currentCourseSession.value) {
|
if (contentId && currentCourseSession.value) {
|
||||||
return currentCourseSession.value.assignment_details_list.find(
|
return currentCourseSession.value.assignments.find(
|
||||||
(assignmentDetails) => assignmentDetails.learningContentId === contentId
|
(a) => a.learning_content_id === contentId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +259,8 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
|
||||||
startUpload,
|
startUpload,
|
||||||
removeDocument,
|
removeDocument,
|
||||||
findAttendanceCourse,
|
findAttendanceCourse,
|
||||||
findAssignmentDetails,
|
findCourseSessionAssignment,
|
||||||
|
allDueDates,
|
||||||
|
|
||||||
// use `useCurrentCourseSession` whenever possible
|
// use `useCurrentCourseSession` whenever possible
|
||||||
currentCourseSession,
|
currentCourseSession,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { AssignmentCompletionStatus as AssignmentCompletionStatusGenerated } from "@/gql/graphql";
|
import type { AssignmentCompletionStatus as AssignmentCompletionStatusGenerated } from "@/gql/graphql";
|
||||||
import type { Circle } from "@/services/circle";
|
import type { Circle } from "@/services/circle";
|
||||||
|
import type { Dayjs } from "dayjs";
|
||||||
import type { Component } from "vue";
|
import type { Component } from "vue";
|
||||||
|
|
||||||
export type LoginMethod = "local" | "sso";
|
export type LoginMethod = "local" | "sso";
|
||||||
|
|
@ -412,17 +413,24 @@ export interface CircleDocument {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CourseSessionAttendanceCourse {
|
export interface CourseSessionAttendanceCourse {
|
||||||
learningContentId: number;
|
id: number;
|
||||||
|
course_session_id: number;
|
||||||
|
learning_content_id: number;
|
||||||
start: string;
|
start: string;
|
||||||
end: string;
|
end: string;
|
||||||
location: string;
|
location: string;
|
||||||
trainer: string;
|
trainer: string;
|
||||||
|
due_date_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CourseSessionAssignmentDetails {
|
export interface CourseSessionAssignment {
|
||||||
learningContentId: number;
|
id: number;
|
||||||
submissionDeadlineDateTimeUtc: string;
|
course_session_id: number;
|
||||||
evaluationDeadlineDateTimeUtc: string;
|
learning_content_id: number;
|
||||||
|
submission_deadline_id: number;
|
||||||
|
submission_deadline_start: string;
|
||||||
|
evaluation_deadline_id: number;
|
||||||
|
evaluation_deadline_start: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CourseSession {
|
export interface CourseSession {
|
||||||
|
|
@ -439,9 +447,10 @@ export interface CourseSession {
|
||||||
course_url: string;
|
course_url: string;
|
||||||
media_library_url: string;
|
media_library_url: string;
|
||||||
attendance_courses: CourseSessionAttendanceCourse[];
|
attendance_courses: CourseSessionAttendanceCourse[];
|
||||||
assignment_details_list: CourseSessionAssignmentDetails[];
|
assignments: CourseSessionAssignment[];
|
||||||
documents: CircleDocument[];
|
documents: CircleDocument[];
|
||||||
users: CourseSessionUser[];
|
users: CourseSessionUser[];
|
||||||
|
due_dates: DueDate[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Role = "MEMBER" | "EXPERT" | "TUTOR";
|
export type Role = "MEMBER" | "EXPERT" | "TUTOR";
|
||||||
|
|
@ -556,3 +565,15 @@ export interface UserAssignmentCompletionStatus {
|
||||||
completion_status: AssignmentCompletionStatus;
|
completion_status: AssignmentCompletionStatus;
|
||||||
evaluation_grade: number | null;
|
evaluation_grade: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DueDate = {
|
||||||
|
id: number;
|
||||||
|
start: Dayjs;
|
||||||
|
end: Dayjs;
|
||||||
|
title: string;
|
||||||
|
learning_content_description: string;
|
||||||
|
description: string;
|
||||||
|
url: string;
|
||||||
|
course_session: number | null;
|
||||||
|
page: number | null;
|
||||||
|
};
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -116,10 +116,12 @@ LOCAL_APPS = [
|
||||||
"vbv_lernwelt.learnpath",
|
"vbv_lernwelt.learnpath",
|
||||||
"vbv_lernwelt.competence",
|
"vbv_lernwelt.competence",
|
||||||
"vbv_lernwelt.media_library",
|
"vbv_lernwelt.media_library",
|
||||||
|
"vbv_lernwelt.course_session",
|
||||||
"vbv_lernwelt.feedback",
|
"vbv_lernwelt.feedback",
|
||||||
"vbv_lernwelt.files",
|
"vbv_lernwelt.files",
|
||||||
"vbv_lernwelt.notify",
|
"vbv_lernwelt.notify",
|
||||||
"vbv_lernwelt.assignment",
|
"vbv_lernwelt.assignment",
|
||||||
|
"vbv_lernwelt.duedate",
|
||||||
]
|
]
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import json
|
import json
|
||||||
|
import random
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import wagtail_factories
|
import wagtail_factories
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from wagtail.models import Site
|
from wagtail.models import Site
|
||||||
from wagtail.rich_text import RichText
|
from wagtail.rich_text import RichText
|
||||||
|
|
@ -34,7 +37,15 @@ from vbv_lernwelt.course.models import (
|
||||||
CourseSession,
|
CourseSession,
|
||||||
CourseSessionUser,
|
CourseSessionUser,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.learnpath.models import Circle
|
from vbv_lernwelt.course_session.models import (
|
||||||
|
CourseSessionAssignment,
|
||||||
|
CourseSessionAttendanceCourse,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.learnpath.models import (
|
||||||
|
Circle,
|
||||||
|
LearningContentAssignment,
|
||||||
|
LearningContentAttendanceCourse,
|
||||||
|
)
|
||||||
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
||||||
CircleFactory,
|
CircleFactory,
|
||||||
LearningContentAssignmentFactory,
|
LearningContentAssignmentFactory,
|
||||||
|
|
@ -79,16 +90,19 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
|
||||||
create_test_media_library()
|
create_test_media_library()
|
||||||
|
|
||||||
if with_sessions:
|
if with_sessions:
|
||||||
|
now = timezone.now()
|
||||||
# course sessions
|
# course sessions
|
||||||
cs_bern = CourseSession.objects.create(
|
cs_bern = CourseSession.objects.create(
|
||||||
course_id=COURSE_TEST_ID,
|
course_id=COURSE_TEST_ID,
|
||||||
title="Test Bern 2022 a",
|
title="Test Bern 2022 a",
|
||||||
id=TEST_COURSE_SESSION_BERN_ID,
|
id=TEST_COURSE_SESSION_BERN_ID,
|
||||||
|
start_date=now,
|
||||||
)
|
)
|
||||||
cs_zurich = CourseSession.objects.create(
|
cs_zurich = CourseSession.objects.create(
|
||||||
course_id=COURSE_TEST_ID,
|
course_id=COURSE_TEST_ID,
|
||||||
title="Test Zürich 2022 a",
|
title="Test Zürich 2022 a",
|
||||||
id=TEST_COURSE_SESSION_ZURICH_ID,
|
id=TEST_COURSE_SESSION_ZURICH_ID,
|
||||||
|
start_date=now,
|
||||||
)
|
)
|
||||||
|
|
||||||
trainer1 = User.objects.get(email="test-trainer1@example.com")
|
trainer1 = User.objects.get(email="test-trainer1@example.com")
|
||||||
|
|
@ -115,6 +129,18 @@ def create_test_course(include_uk=True, include_vv=True, with_sessions=False):
|
||||||
course_session=cs_zurich,
|
course_session=cs_zurich,
|
||||||
user=student2,
|
user=student2,
|
||||||
)
|
)
|
||||||
|
course = Course.objects.get(id=COURSE_TEST_ID)
|
||||||
|
for cs in CourseSession.objects.filter(course_id=COURSE_TEST_ID):
|
||||||
|
for assignment in LearningContentAssignment.objects.descendant_of(
|
||||||
|
course.coursepage
|
||||||
|
):
|
||||||
|
create_course_session_assignment(cs, assignment)
|
||||||
|
for (
|
||||||
|
attendance_course
|
||||||
|
) in LearningContentAttendanceCourse.objects.descendant_of(
|
||||||
|
course.coursepage
|
||||||
|
):
|
||||||
|
create_course_session_attendance_course(cs, attendance_course)
|
||||||
|
|
||||||
return course
|
return course
|
||||||
|
|
||||||
|
|
@ -143,6 +169,45 @@ def create_test_assignment_submitted_data(assignment, course_session, user):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_course_session_assignment(course_session, assignment):
|
||||||
|
csa, created = CourseSessionAssignment.objects.get_or_create(
|
||||||
|
course_session=course_session,
|
||||||
|
learning_content=assignment,
|
||||||
|
)
|
||||||
|
|
||||||
|
if course_session.start_date is None:
|
||||||
|
course_session.start_date = datetime.now() + timedelta(days=12)
|
||||||
|
course_session.save()
|
||||||
|
submission_deadline = csa.submission_deadline
|
||||||
|
if submission_deadline:
|
||||||
|
submission_deadline.start = course_session.start_date + timedelta(days=14)
|
||||||
|
submission_deadline.save()
|
||||||
|
evaluation_deadline = csa.evaluation_deadline
|
||||||
|
if evaluation_deadline:
|
||||||
|
evaluation_deadline.start = course_session.start_date + timedelta(days=28)
|
||||||
|
evaluation_deadline.save()
|
||||||
|
return csa
|
||||||
|
|
||||||
|
|
||||||
|
def create_course_session_attendance_course(course_session, course):
|
||||||
|
casc = CourseSessionAttendanceCourse.objects.create(
|
||||||
|
course_session=course_session,
|
||||||
|
learning_content=course,
|
||||||
|
location="Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern",
|
||||||
|
trainer="Roland Grossenbacher, roland.grossenbacher@helvetia.ch",
|
||||||
|
)
|
||||||
|
random_week = random.randint(1, 26)
|
||||||
|
|
||||||
|
casc.due_date.start = timezone.make_aware(
|
||||||
|
datetime(2023, 6, 14, 8, 30) + timedelta(weeks=random_week)
|
||||||
|
)
|
||||||
|
casc.due_date.end = timezone.make_aware(
|
||||||
|
datetime(2023, 6, 14, 17, 0) + timedelta(weeks=random_week)
|
||||||
|
)
|
||||||
|
casc.due_date.save()
|
||||||
|
return casc
|
||||||
|
|
||||||
|
|
||||||
def create_test_course_with_categories(apps=None, schema_editor=None):
|
def create_test_course_with_categories(apps=None, schema_editor=None):
|
||||||
if apps is not None:
|
if apps is not None:
|
||||||
Course = apps.get_model("course", "Course")
|
Course = apps.get_model("course", "Course")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import djclick as click
|
import djclick as click
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from vbv_lernwelt.assignment.creators.create_assignments import (
|
from vbv_lernwelt.assignment.creators.create_assignments import (
|
||||||
create_uk_basis_prep_assignment,
|
create_uk_basis_prep_assignment,
|
||||||
|
|
@ -68,6 +70,10 @@ from vbv_lernwelt.course.models import (
|
||||||
CourseSessionUser,
|
CourseSessionUser,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course.services import mark_course_completion
|
from vbv_lernwelt.course.services import mark_course_completion
|
||||||
|
from vbv_lernwelt.course_session.models import (
|
||||||
|
CourseSessionAssignment,
|
||||||
|
CourseSessionAttendanceCourse,
|
||||||
|
)
|
||||||
from vbv_lernwelt.feedback.creators.create_demo_feedback import create_feedback
|
from vbv_lernwelt.feedback.creators.create_demo_feedback import create_feedback
|
||||||
from vbv_lernwelt.importer.services import (
|
from vbv_lernwelt.importer.services import (
|
||||||
import_course_sessions_from_excel,
|
import_course_sessions_from_excel,
|
||||||
|
|
@ -237,35 +243,34 @@ def create_course_uk_de():
|
||||||
cs = CourseSession.objects.create(
|
cs = CourseSession.objects.create(
|
||||||
course_id=COURSE_UK,
|
course_id=COURSE_UK,
|
||||||
title="Bern 2023 a",
|
title="Bern 2023 a",
|
||||||
attendance_courses=[
|
|
||||||
{
|
|
||||||
"learningContentId": LearningContentAttendanceCourse.objects.get(
|
|
||||||
slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug"
|
|
||||||
).id,
|
|
||||||
"start": "2023-05-23T08:30:00+0200",
|
|
||||||
"end": "2023-05-23T17:00:00+0200",
|
|
||||||
"location": "Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern",
|
|
||||||
"trainer": "Roland Grossenbacher, roland.grossenbacher@helvetia.ch",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
assignment_details_list=[
|
|
||||||
{
|
|
||||||
"learningContentId": LearningContentAssignment.objects.get(
|
|
||||||
slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"
|
|
||||||
).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",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for i, cs in enumerate(CourseSession.objects.filter(course_id=COURSE_UK_TRAINING)):
|
||||||
|
create_course_session_assignments(
|
||||||
|
cs,
|
||||||
|
f"{course.slug}-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice",
|
||||||
|
i=i,
|
||||||
|
)
|
||||||
|
|
||||||
|
csac = CourseSessionAttendanceCourse.objects.create(
|
||||||
|
course_session=cs,
|
||||||
|
learning_content=LearningContentAttendanceCourse.objects.get(
|
||||||
|
slug="überbetriebliche-kurse-lp-circle-fahrzeug-lc-präsenzkurs-fahrzeug"
|
||||||
|
),
|
||||||
|
location="Handelsschule KV Bern, Zimmer 123, Eigerstrasse 16, 3012 Bern",
|
||||||
|
trainer="Roland Grossenbacher, roland.grossenbacher@helvetia.ch",
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: create dates schlauer
|
||||||
|
random_week = random.randint(1, 26)
|
||||||
|
csac.due_date.start = timezone.make_aware(
|
||||||
|
datetime(2023, 6, 14, 8, 30) + timedelta(weeks=random_week)
|
||||||
|
)
|
||||||
|
csac.due_date.end = timezone.make_aware(
|
||||||
|
datetime(2023, 6, 14, 17, 0) + timedelta(weeks=random_week)
|
||||||
|
)
|
||||||
|
csac.due_date.save()
|
||||||
|
|
||||||
# figma demo users and data
|
# figma demo users and data
|
||||||
csu = CourseSessionUser.objects.create(
|
csu = CourseSessionUser.objects.create(
|
||||||
course_session=cs,
|
course_session=cs,
|
||||||
|
|
@ -537,24 +542,12 @@ def create_course_training_de():
|
||||||
f"{current_dir}/../../../importer/tests/Schulungen_Teilnehmende.xlsx",
|
f"{current_dir}/../../../importer/tests/Schulungen_Teilnehmende.xlsx",
|
||||||
)
|
)
|
||||||
|
|
||||||
for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING):
|
for i, cs in enumerate(CourseSession.objects.filter(course_id=COURSE_UK_TRAINING)):
|
||||||
cs.assignment_details_list = [
|
create_course_session_assignments(
|
||||||
{
|
cs,
|
||||||
"learningContentId": LearningContentAssignment.objects.get(
|
f"{course.slug}-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice",
|
||||||
slug=f"{course.slug}-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice"
|
i=i,
|
||||||
).id,
|
)
|
||||||
"submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
|
||||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"learningContentId": LearningContentAssignment.objects.get(
|
|
||||||
slug=f"{course.slug}-lp-circle-fahrzeug-lc-fahrzeug-mein-erstes-auto"
|
|
||||||
).id,
|
|
||||||
"submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
|
||||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
cs.save()
|
|
||||||
|
|
||||||
# attach users as trainers to ÜK course
|
# attach users as trainers to ÜK course
|
||||||
course_uk = Course.objects.filter(id=COURSE_UK).first()
|
course_uk = Course.objects.filter(id=COURSE_UK).first()
|
||||||
|
|
@ -586,6 +579,24 @@ def create_course_training_de():
|
||||||
csu.save()
|
csu.save()
|
||||||
|
|
||||||
|
|
||||||
|
def create_course_session_assignments(course_session, assignment_slug, i=1):
|
||||||
|
csa = CourseSessionAssignment.objects.create(
|
||||||
|
course_session=course_session,
|
||||||
|
learning_content=LearningContentAssignment.objects.get(slug=assignment_slug),
|
||||||
|
)
|
||||||
|
if course_session.start_date is None:
|
||||||
|
course_session.start_date = datetime.now() + timedelta(days=i * 12)
|
||||||
|
course_session.save()
|
||||||
|
submission_deadline = csa.submission_deadline
|
||||||
|
if submission_deadline:
|
||||||
|
submission_deadline.start = course_session.start_date + timedelta(days=14)
|
||||||
|
submission_deadline.save()
|
||||||
|
evaluation_deadline = csa.evaluation_deadline
|
||||||
|
if evaluation_deadline:
|
||||||
|
evaluation_deadline.start = course_session.start_date + timedelta(days=28)
|
||||||
|
evaluation_deadline.save()
|
||||||
|
|
||||||
|
|
||||||
def create_course_training_fr():
|
def create_course_training_fr():
|
||||||
# Test Lehrgang für üK Trainer FR
|
# Test Lehrgang für üK Trainer FR
|
||||||
course = create_versicherungsvermittlerin_with_categories(
|
course = create_versicherungsvermittlerin_with_categories(
|
||||||
|
|
@ -622,22 +633,22 @@ def create_course_training_fr():
|
||||||
)
|
)
|
||||||
|
|
||||||
for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING_FR):
|
for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING_FR):
|
||||||
cs.assignment_details_list = [
|
# cs.assignment_details_list = [
|
||||||
{
|
# {
|
||||||
"learningContentId": LearningContentAssignment.objects.get(
|
# "learningContentId": LearningContentAssignment.objects.get(
|
||||||
slug=f"{course.slug}-lp-circle-véhicule-lc-vérification-dune-police-dassurance-de-véhicule-à-moteur"
|
# slug=f"{course.slug}-lp-circle-véhicule-lc-vérification-dune-police-dassurance-de-véhicule-à-moteur"
|
||||||
).id,
|
# ).id,
|
||||||
"submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
# "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
||||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
# "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||||
},
|
# },
|
||||||
{
|
# {
|
||||||
"learningContentId": LearningContentAssignment.objects.get(
|
# "learningContentId": LearningContentAssignment.objects.get(
|
||||||
slug=f"{course.slug}-lp-circle-véhicule-lc-véhicule-à-moteur-ma-première-voiture"
|
# slug=f"{course.slug}-lp-circle-véhicule-lc-véhicule-à-moteur-ma-première-voiture"
|
||||||
).id,
|
# ).id,
|
||||||
"submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
# "submissionDeadlineDateTimeUtc": "2023-06-13T19:00:00Z",
|
||||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
# "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||||
},
|
# },
|
||||||
]
|
# ]
|
||||||
cs.save()
|
cs.save()
|
||||||
|
|
||||||
# attach users as trainers to ÜK course
|
# attach users as trainers to ÜK course
|
||||||
|
|
@ -709,22 +720,22 @@ def create_course_training_it():
|
||||||
)
|
)
|
||||||
|
|
||||||
for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING_IT):
|
for cs in CourseSession.objects.filter(course_id=COURSE_UK_TRAINING_IT):
|
||||||
cs.assignment_details_list = [
|
# cs.assignment_details_list = [
|
||||||
{
|
# {
|
||||||
"learningContentId": LearningContentAssignment.objects.get(
|
# "learningContentId": LearningContentAssignment.objects.get(
|
||||||
slug=f"{course.slug}-lp-circle-veicolo-lc-verifica-di-una-polizza-di-assicurazione-veicoli-a-motore"
|
# slug=f"{course.slug}-lp-circle-veicolo-lc-verifica-di-una-polizza-di-assicurazione-veicoli-a-motore"
|
||||||
).id,
|
# ).id,
|
||||||
"submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z",
|
# "submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z",
|
||||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
# "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||||
},
|
# },
|
||||||
{
|
# {
|
||||||
"learningContentId": LearningContentAssignment.objects.get(
|
# "learningContentId": LearningContentAssignment.objects.get(
|
||||||
slug=f"{course.slug}-lp-circle-veicolo-lc-veicolo-la-mia-prima-auto"
|
# slug=f"{course.slug}-lp-circle-veicolo-lc-veicolo-la-mia-prima-auto"
|
||||||
).id,
|
# ).id,
|
||||||
"submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z",
|
# "submissionDeadlineDateTimeUtc": "2023-06-20T19:00:00Z",
|
||||||
"evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
# "evaluationDeadlineDateTimeUtc": "2023-06-27T19:00:00Z",
|
||||||
},
|
# },
|
||||||
]
|
# ]
|
||||||
cs.save()
|
cs.save()
|
||||||
|
|
||||||
# attach users as trainers to ÜK course
|
# attach users as trainers to ÜK course
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-14 14:02
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("course", "0004_import_fields"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="coursesession",
|
||||||
|
name="attendance_courses",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-21 14:19
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("course", "0005_remove_coursesession_attendance_courses"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="coursesession",
|
||||||
|
name="assignment_details_list",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -2,7 +2,6 @@ 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 grapple.models import GraphQLString
|
from grapple.models import GraphQLString
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
|
@ -193,24 +192,6 @@ 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_COURSES_SCHEMA = {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"learningContentId": {
|
|
||||||
"type": "number",
|
|
||||||
"title": "ID des Lerninhalts",
|
|
||||||
"required": True,
|
|
||||||
},
|
|
||||||
"start": {"type": "string", "format": "datetime"},
|
|
||||||
"end": {"type": "string", "format": "datetime"},
|
|
||||||
"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)
|
||||||
|
|
||||||
|
|
@ -226,11 +207,6 @@ 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_courses = JSONField(
|
|
||||||
schema=ATTENDANCE_COURSES_SCHEMA, blank=True, default=list
|
|
||||||
)
|
|
||||||
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)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,16 @@ from vbv_lernwelt.course.models import (
|
||||||
CourseCompletion,
|
CourseCompletion,
|
||||||
CourseSession,
|
CourseSession,
|
||||||
)
|
)
|
||||||
|
from vbv_lernwelt.course_session.models import (
|
||||||
|
CourseSessionAssignment,
|
||||||
|
CourseSessionAttendanceCourse,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.course_session.serializers import (
|
||||||
|
CourseSessionAssignmentSerializer,
|
||||||
|
CourseSessionAttendanceCourseSerializer,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
|
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
||||||
|
|
||||||
|
|
||||||
class CourseSerializer(serializers.ModelSerializer):
|
class CourseSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -50,6 +60,9 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
||||||
competence_url = serializers.SerializerMethodField()
|
competence_url = serializers.SerializerMethodField()
|
||||||
media_library_url = serializers.SerializerMethodField()
|
media_library_url = serializers.SerializerMethodField()
|
||||||
documents = serializers.SerializerMethodField()
|
documents = serializers.SerializerMethodField()
|
||||||
|
attendance_courses = serializers.SerializerMethodField()
|
||||||
|
assignments = serializers.SerializerMethodField()
|
||||||
|
due_dates = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def get_course(self, obj):
|
def get_course(self, obj):
|
||||||
return CourseSerializer(obj.course).data
|
return CourseSerializer(obj.course).data
|
||||||
|
|
@ -75,6 +88,20 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
return CircleDocumentSerializer(documents, many=True).data
|
return CircleDocumentSerializer(documents, many=True).data
|
||||||
|
|
||||||
|
def get_attendance_courses(self, obj):
|
||||||
|
return CourseSessionAttendanceCourseSerializer(
|
||||||
|
CourseSessionAttendanceCourse.objects.filter(course_session=obj), many=True
|
||||||
|
).data
|
||||||
|
|
||||||
|
def get_assignments(self, obj):
|
||||||
|
return CourseSessionAssignmentSerializer(
|
||||||
|
CourseSessionAssignment.objects.filter(course_session=obj), many=True
|
||||||
|
).data
|
||||||
|
|
||||||
|
def get_due_dates(self, obj):
|
||||||
|
due_dates = DueDate.objects.filter(course_session=obj)
|
||||||
|
return DueDateSerializer(due_dates, many=True).data
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CourseSession
|
model = CourseSession
|
||||||
fields = [
|
fields = [
|
||||||
|
|
@ -87,13 +114,14 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
||||||
"end_date",
|
"end_date",
|
||||||
"additional_json_data",
|
"additional_json_data",
|
||||||
"attendance_courses",
|
"attendance_courses",
|
||||||
"assignment_details_list",
|
"assignments",
|
||||||
"learning_path_url",
|
"learning_path_url",
|
||||||
"cockpit_url",
|
"cockpit_url",
|
||||||
"competence_url",
|
"competence_url",
|
||||||
"media_library_url",
|
"media_library_url",
|
||||||
"course_url",
|
"course_url",
|
||||||
"documents",
|
"documents",
|
||||||
|
"due_dates",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from vbv_lernwelt.course_session.models import (
|
||||||
|
CourseSessionAssignment,
|
||||||
|
CourseSessionAttendanceCourse,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(CourseSessionAttendanceCourse)
|
||||||
|
class CourseSessionAttendanceCourseAdmin(admin.ModelAdmin):
|
||||||
|
# Inline fields are not possible for the DueDate model, because it is not a ForeignKey relatoion.
|
||||||
|
readonly_fields = ["course_session", "learning_content", "due_date"]
|
||||||
|
list_display = [
|
||||||
|
"course_session",
|
||||||
|
"learning_content",
|
||||||
|
"trainer",
|
||||||
|
]
|
||||||
|
list_filter = ["course_session__course", "course_session"]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(CourseSessionAssignment)
|
||||||
|
class CourseSessionAssignmentAdmin(admin.ModelAdmin):
|
||||||
|
# Inline fields are not possible for the DueDate model, because it is not a ForeignKey relatoion.
|
||||||
|
readonly_fields = ["course_session", "learning_content"]
|
||||||
|
list_display = [
|
||||||
|
"course_session",
|
||||||
|
"learning_content",
|
||||||
|
]
|
||||||
|
list_filter = ["course_session__course"]
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "vbv_lernwelt.course_session"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
try:
|
||||||
|
# pylint: disable=unused-import,import-outside-toplevel
|
||||||
|
import vbv_lernwelt.course_session.signals # noqa F401
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import factory
|
||||||
|
from factory.django import DjangoModelFactory
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.factories import CourseFactory
|
||||||
|
from vbv_lernwelt.course.models import CourseSession
|
||||||
|
from vbv_lernwelt.course_session.models import (
|
||||||
|
CourseSessionAssignment,
|
||||||
|
CourseSessionAttendanceCourse,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionAssignmentFactory(DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = CourseSessionAssignment
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionAttendanceCourseFactory(DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = CourseSessionAttendanceCourse
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionFactory(DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = CourseSession
|
||||||
|
|
||||||
|
course = CourseFactory()
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-14 15:01
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("learnpath", "0007_learningunit_title_hidden"),
|
||||||
|
("course", "0005_remove_coursesession_attendance_courses"),
|
||||||
|
("duedate", "0002_auto_20230614_1500"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="CourseSessionAttendanceCourse",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("location", models.CharField(blank=True, default="", max_length=255)),
|
||||||
|
("trainer", models.CharField(blank=True, default="", max_length=255)),
|
||||||
|
(
|
||||||
|
"course_session",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="course.coursesession",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"due_date",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="attendance_course_due_date",
|
||||||
|
to="duedate.duedate",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"learning_content",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="learnpath.learningcontentattendancecourse",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-21 14:19
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("course", "0006_remove_coursesession_assignment_details_list"),
|
||||||
|
("learnpath", "0007_learningunit_title_hidden"),
|
||||||
|
("duedate", "0003_alter_duedate_course_session"),
|
||||||
|
("course_session", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="CourseSessionAssignment",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"course_session",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="course.coursesession",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"evaluation_deadline",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="assignment_evaluation_deadline",
|
||||||
|
to="duedate.duedate",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"learning_content",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="learnpath.learningcontentassignment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"submission_deadline",
|
||||||
|
models.OneToOneField(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="assignment_submission_deadline",
|
||||||
|
to="duedate.duedate",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-28 11:21
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("duedate", "0006_auto_20230627_1553"),
|
||||||
|
("course_session", "0002_coursesessionassignment"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="coursesessionassignment",
|
||||||
|
name="evaluation_deadline",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="assignment_evaluation_deadline",
|
||||||
|
to="duedate.duedate",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="coursesessionassignment",
|
||||||
|
name="submission_deadline",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="assignment_submission_deadline",
|
||||||
|
to="duedate.duedate",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from vbv_lernwelt.assignment.models import AssignmentType
|
||||||
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionAttendanceCourse(models.Model):
|
||||||
|
"""
|
||||||
|
Präsenztag Durchührung
|
||||||
|
|
||||||
|
Kann über einen Zeitraum von meheren Tagen gehen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
course_session = models.ForeignKey(
|
||||||
|
"course.CourseSession",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
|
learning_content = models.ForeignKey(
|
||||||
|
"learnpath.LearningContentAttendanceCourse",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
|
due_date = models.OneToOneField(
|
||||||
|
"duedate.DueDate",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="attendance_course_due_date",
|
||||||
|
)
|
||||||
|
|
||||||
|
location = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
trainer = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.pk:
|
||||||
|
title = ""
|
||||||
|
url = ""
|
||||||
|
page = None
|
||||||
|
if self.learning_content_id:
|
||||||
|
title = self.learning_content.title
|
||||||
|
page = self.learning_content.page_ptr
|
||||||
|
url = self.learning_content.get_frontend_url()
|
||||||
|
|
||||||
|
self.due_date = DueDate.objects.create(
|
||||||
|
url=url,
|
||||||
|
title=f"{title}",
|
||||||
|
learning_content_description=f"{_('Präsenzkurs')}",
|
||||||
|
description="",
|
||||||
|
course_session=self.course_session,
|
||||||
|
page=page,
|
||||||
|
)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.course_session} - {self.learning_content}"
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionAssignment(models.Model):
|
||||||
|
"""
|
||||||
|
Auftrag
|
||||||
|
- Geletitete Fallarbeit ist eine speziefische ausprägung eines Auftrags (assignment_type)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
course_session = models.ForeignKey(
|
||||||
|
"course.CourseSession",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
|
learning_content = models.ForeignKey(
|
||||||
|
"learnpath.LearningContentAssignment",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
|
submission_deadline = models.OneToOneField(
|
||||||
|
"duedate.DueDate",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="assignment_submission_deadline",
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
evaluation_deadline = models.OneToOneField(
|
||||||
|
"duedate.DueDate",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="assignment_evaluation_deadline",
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.pk:
|
||||||
|
title = ""
|
||||||
|
url = ""
|
||||||
|
page = None
|
||||||
|
assignment_type_description = ""
|
||||||
|
if self.learning_content_id:
|
||||||
|
title = self.learning_content.title
|
||||||
|
page = self.learning_content.page_ptr
|
||||||
|
url = self.learning_content.get_frontend_url()
|
||||||
|
assignment_type = self.learning_content.assignment_type
|
||||||
|
assignment_type_descriptions = {
|
||||||
|
AssignmentType.CASEWORK.name: _("Geleitete Fallarbeit"),
|
||||||
|
AssignmentType.PREP_ASSIGNMENT.name: _("Vorbereitungsauftrag"),
|
||||||
|
AssignmentType.REFLECTION.name: _("Reflexion"),
|
||||||
|
}
|
||||||
|
assignment_type_description = assignment_type_descriptions.get(
|
||||||
|
assignment_type, ""
|
||||||
|
)
|
||||||
|
|
||||||
|
if assignment_type in {
|
||||||
|
AssignmentType.CASEWORK.value,
|
||||||
|
AssignmentType.PREP_ASSIGNMENT.value,
|
||||||
|
}: # Reflexion
|
||||||
|
self.submission_deadline = DueDate.objects.create(
|
||||||
|
url=url,
|
||||||
|
title=f"{title}",
|
||||||
|
learning_content_description=assignment_type_description,
|
||||||
|
description=f"{_('Abgabe Termin')}",
|
||||||
|
course_session=self.course_session,
|
||||||
|
page=page,
|
||||||
|
)
|
||||||
|
|
||||||
|
if assignment_type == AssignmentType.CASEWORK.value:
|
||||||
|
self.evaluation_deadline = DueDate.objects.create(
|
||||||
|
url=url,
|
||||||
|
title=f"{title}",
|
||||||
|
learning_content_description=assignment_type_description,
|
||||||
|
description=f"{_('Freigabe Termin Bewertungen')}",
|
||||||
|
course_session=self.course_session,
|
||||||
|
page=page,
|
||||||
|
)
|
||||||
|
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.course_session} - {self.learning_content}"
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from vbv_lernwelt.course_session.models import (
|
||||||
|
CourseSessionAssignment,
|
||||||
|
CourseSessionAttendanceCourse,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionAttendanceCourseSerializer(serializers.ModelSerializer):
|
||||||
|
start = serializers.SerializerMethodField()
|
||||||
|
end = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CourseSessionAttendanceCourse
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"course_session_id",
|
||||||
|
"learning_content_id",
|
||||||
|
"due_date_id",
|
||||||
|
"location",
|
||||||
|
"trainer",
|
||||||
|
"start",
|
||||||
|
"end",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_start(self, obj):
|
||||||
|
return obj.due_date.start
|
||||||
|
|
||||||
|
def get_end(self, obj):
|
||||||
|
return obj.due_date.end
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionAssignmentSerializer(serializers.ModelSerializer):
|
||||||
|
submission_deadline_start = serializers.SerializerMethodField()
|
||||||
|
evaluation_deadline_start = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CourseSessionAssignment
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"course_session_id",
|
||||||
|
"learning_content_id",
|
||||||
|
"submission_deadline_id",
|
||||||
|
"submission_deadline_start",
|
||||||
|
"evaluation_deadline_id",
|
||||||
|
"evaluation_deadline_start",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_evaluation_deadline_start(self, obj):
|
||||||
|
if obj.evaluation_deadline:
|
||||||
|
return obj.evaluation_deadline.start
|
||||||
|
|
||||||
|
def get_submission_deadline_start(self, obj):
|
||||||
|
if obj.submission_deadline:
|
||||||
|
return obj.submission_deadline.start
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.create_default_users import create_default_users
|
||||||
|
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||||
|
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||||
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
|
|
||||||
|
|
||||||
|
class CourseSessionModelsTestCase(TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
create_default_users()
|
||||||
|
create_test_course(with_sessions=True)
|
||||||
|
|
||||||
|
def test_course_session_attendance_course(self):
|
||||||
|
csac = CourseSessionAttendanceCourse.objects.all().first()
|
||||||
|
|
||||||
|
due_date = csac.due_date
|
||||||
|
|
||||||
|
deadline_date = datetime(
|
||||||
|
2023, 7, 6, 8, 30, tzinfo=timezone.get_current_timezone()
|
||||||
|
)
|
||||||
|
due_date.start = deadline_date
|
||||||
|
due_date.save()
|
||||||
|
|
||||||
|
this_date = DueDate.objects.get(pk=due_date.pk)
|
||||||
|
self.assertEqual(this_date.start, deadline_date)
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
|
from vbv_lernwelt.learnpath.models import (
|
||||||
|
LearningContentAttendanceCourse,
|
||||||
|
LearningContentTest,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
@admin.register(DueDate)
|
||||||
|
class DueDateAdmin(admin.ModelAdmin):
|
||||||
|
date_hierarchy = "end"
|
||||||
|
list_display = ["title", "course_session", "start", "end", "is_undefined"]
|
||||||
|
list_filter = ["course_session"]
|
||||||
|
readonly_fields = ["course_session", "page"]
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||||
|
if db_field.name == "page":
|
||||||
|
if request.resolver_match.kwargs.get("object_id"):
|
||||||
|
object_id = int(request.resolver_match.kwargs.get("object_id"))
|
||||||
|
csd = DueDate.objects.get(id=object_id)
|
||||||
|
kwargs["queryset"] = Page.objects.descendant_of(
|
||||||
|
csd.course_session.course.coursepage
|
||||||
|
).exact_type(LearningContentAttendanceCourse, LearningContentTest)
|
||||||
|
else:
|
||||||
|
kwargs["queryset"] = Page.objects.none()
|
||||||
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DueDatesConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "vbv_lernwelt.duedate"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
try:
|
||||||
|
# pylint: disable=unused-import,import-outside-toplevel
|
||||||
|
import vbv_lernwelt.course.signals # noqa F401
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
from django.utils import timezone
|
||||||
|
from factory.django import DjangoModelFactory
|
||||||
|
|
||||||
|
from .models import DueDate
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_date(date_string):
|
||||||
|
return datetime.datetime.strptime(
|
||||||
|
date_string,
|
||||||
|
"%b %d %Y",
|
||||||
|
).astimezone(timezone.get_current_timezone())
|
||||||
|
|
||||||
|
|
||||||
|
class DueDateFactory(DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = DueDate
|
||||||
|
|
||||||
|
title = "Prüfung Versicherungsvermittler/-in"
|
||||||
|
end = get_date("Jan 01 2021")
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-14 09:29
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("course", "0004_import_fields"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="DueDate",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("start", models.DateTimeField(db_index=True, null=True)),
|
||||||
|
("end", models.DateTimeField(db_index=True, null=True)),
|
||||||
|
("title", models.CharField(default="Termin", max_length=1024)),
|
||||||
|
("url", models.URLField(blank=True, max_length=1024, null=True)),
|
||||||
|
(
|
||||||
|
"learning_content_id",
|
||||||
|
models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"course_session",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="events",
|
||||||
|
to="course.coursesession",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-14 13:00
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("wagtailcore", "0083_workflowcontenttype"),
|
||||||
|
("duedate", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="learning_content_id",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="page",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="wagtailcore.page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-21 14:19
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("course", "0006_remove_coursesession_assignment_details_list"),
|
||||||
|
("duedate", "0002_auto_20230614_1500"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="course_session",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="duedates",
|
||||||
|
to="course.coursesession",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-21 15:03
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("contenttypes", "0002_remove_content_type_name"),
|
||||||
|
("duedate", "0003_alter_duedate_course_session"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="content_type",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="object_id",
|
||||||
|
field=models.PositiveIntegerField(null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-22 09:38
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("duedate", "0004_auto_20230621_1703"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="content_type",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="object_id",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-27 13:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("duedate", "0005_auto_20230622_1138"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="description",
|
||||||
|
field=models.CharField(default="Abgabetermin", max_length=1024),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="learning_content_description",
|
||||||
|
field=models.CharField(default="GeleiteteFallarbeit", max_length=1024),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-07-03 15:41
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("course", "0006_remove_coursesession_assignment_details_list"),
|
||||||
|
("duedate", "0006_auto_20230627_1553"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="course_session",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=1,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="duedates",
|
||||||
|
to="course.coursesession",
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="description",
|
||||||
|
field=models.CharField(default="", max_length=1024),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="learning_content_description",
|
||||||
|
field=models.CharField(default="", max_length=1024),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="url",
|
||||||
|
field=models.URLField(blank=True, default="", max_length=1024),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-07-11 09:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("duedate", "0007_auto_20230703_1741"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="start",
|
||||||
|
field=models.DateTimeField(blank=True, db_index=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="url",
|
||||||
|
field=models.CharField(blank=True, default="", max_length=1024),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-07-12 07:05
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("course", "0006_remove_coursesession_assignment_details_list"),
|
||||||
|
("duedate", "0008_auto_20230711_1116"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="course_session",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="course.coursesession",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-07-12 07:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("duedate", "0009_alter_duedate_course_session"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="description",
|
||||||
|
field=models.CharField(blank=True, default="", max_length=1024),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="end",
|
||||||
|
field=models.DateTimeField(blank=True, db_index=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="learning_content_description",
|
||||||
|
field=models.CharField(blank=True, default="", max_length=1024),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="start",
|
||||||
|
field=models.DateTimeField(db_index=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="duedate",
|
||||||
|
name="title",
|
||||||
|
field=models.CharField(default="", max_length=1024),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.models import User
|
||||||
|
from vbv_lernwelt.course.models import CourseSession
|
||||||
|
|
||||||
|
|
||||||
|
class DueDate(models.Model):
|
||||||
|
start = models.DateTimeField(null=True, db_index=True)
|
||||||
|
end = models.DateTimeField(null=True, blank=True, db_index=True)
|
||||||
|
title = models.CharField(default="", max_length=1024)
|
||||||
|
learning_content_description = models.CharField(
|
||||||
|
default="", blank=True, max_length=1024
|
||||||
|
)
|
||||||
|
description = models.CharField(default="", blank=True, max_length=1024)
|
||||||
|
url = models.CharField(default="", blank=True, max_length=1024)
|
||||||
|
course_session = models.ForeignKey(
|
||||||
|
"course.CourseSession",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
page = models.ForeignKey(Page, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
|
def Meta(self):
|
||||||
|
ordering = ["start", "end"]
|
||||||
|
verbose_name = _("Termin")
|
||||||
|
help = "The start date is mandatory. You can set the end date if you want to have a deadline with a duration."
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.is_undefined:
|
||||||
|
return f"DueDate: {self.title} undefined"
|
||||||
|
start_str = self.start.strftime("%Y-%m-%d %H:%M") if self.start else "-"
|
||||||
|
result = f"DueDate: {self.title} {start_str}"
|
||||||
|
|
||||||
|
if self.end:
|
||||||
|
end_str = self.end.strftime("%Y-%m-%d %H:%M") if self.end else "-"
|
||||||
|
result += f" - {end_str}"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_undefined(self):
|
||||||
|
return self.start is None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def duration(self):
|
||||||
|
if self.end is None:
|
||||||
|
return datetime.timedelta(0)
|
||||||
|
return self.end - self.start
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_users_next_events_qs(
|
||||||
|
cls, user: User, course_session: CourseSession = None, limit=10
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Returns a queryset of all due dates that are relevant for the given user.
|
||||||
|
If course_session is given, only due dates for that course_session are returned.
|
||||||
|
The user is determined by via a course session user of a course_assignment.
|
||||||
|
|
||||||
|
"""
|
||||||
|
qs = cls.get_next_due_dates_qs()
|
||||||
|
|
||||||
|
if course_session:
|
||||||
|
qs = qs.filter(
|
||||||
|
course_session=course_session,
|
||||||
|
course_session__course_assignment__user=user,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
qs = qs.filter(course_session__course_assignment__user=user)
|
||||||
|
|
||||||
|
qs = qs.order_by("start")[:limit]
|
||||||
|
|
||||||
|
return qs
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_next_due_dates_qs(cls):
|
||||||
|
now = timezone.now()
|
||||||
|
qs = cls.objects.filter(start__gte=now)
|
||||||
|
return qs
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
|
|
||||||
|
|
||||||
|
class DueDateSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = DueDate
|
||||||
|
fields = "__all__"
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# class TesDueDatetModel(TestCase):
|
||||||
|
#
|
||||||
|
# def test_get_next_duedate_qs_is_really_next(self):
|
||||||
|
# pass
|
||||||
|
# start = timezone.now() - datetime.timedelta(days=18)
|
||||||
|
# generate_duedates(start=start)
|
||||||
|
# self.assertEqual(DueDate.objects.count(), 20)
|
||||||
|
# self.assertEqual(DueDate.get_next_duedates_qs().count(), 2)
|
||||||
|
|
||||||
|
# def test_event_model_factory_validation(self):
|
||||||
|
# e = DueDateFactory()
|
||||||
|
# e.start = get_date("Jan 01 2021")
|
||||||
|
# e.end = get_date("Jan 02 2021")
|
||||||
|
# e.validate()
|
||||||
|
# self.assertTrue(True)
|
||||||
|
#
|
||||||
|
# def test_event_model_factory_validation_invalid(self):
|
||||||
|
# e = DueDateFactory()
|
||||||
|
# e.start = get_date("Jan 04 2021")
|
||||||
|
# e.end = get_date("Jan 02 2021")
|
||||||
|
# self.assertRaises(ValueError, e.validate)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# def generate_duedates(start=timezone.now()):
|
||||||
|
# for i in range(20):
|
||||||
|
# DueDateFactory(
|
||||||
|
# title=f"{i}",
|
||||||
|
# start=start + datetime.timedelta(days=i),
|
||||||
|
# end=start + datetime.timedelta(days=i, hours=1),
|
||||||
|
# )
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# class TestDueDateSerializer(TestCase):
|
||||||
|
# def test_duedate_serializer(self):
|
||||||
|
# pass
|
||||||
|
# create_test_course()
|
||||||
|
# self.assertEqual(DueDate.objects.count(), 1)
|
||||||
|
#
|
||||||
|
# duedates = DueDate.objects.all()
|
||||||
|
# result = DueDateSerializer(duedates, many=True).data
|
||||||
|
# self.assertEqual(result[0]["title"], "Prüfung Versicherungsvermittler/-in")
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
|
@ -5,6 +5,7 @@ from openpyxl.reader.excel import load_workbook
|
||||||
|
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser
|
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser
|
||||||
|
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||||
from vbv_lernwelt.importer.utils import (
|
from vbv_lernwelt.importer.utils import (
|
||||||
calc_header_tuple_list_from_pyxl_sheet,
|
calc_header_tuple_list_from_pyxl_sheet,
|
||||||
parse_circle_group_string,
|
parse_circle_group_string,
|
||||||
|
|
@ -109,42 +110,37 @@ def create_or_update_course_session(
|
||||||
|
|
||||||
cs.save()
|
cs.save()
|
||||||
for circle in circles:
|
for circle in circles:
|
||||||
|
attendance_course_lp_qs = None
|
||||||
if language == "de":
|
if language == "de":
|
||||||
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
|
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
|
||||||
slug=f"{course.slug}-lp-circle-{circle.lower()}-lc-präsenzkurs-{circle.lower()}"
|
slug=f"{course.slug}-lp-circle-{circle.lower()}-lc-präsenzkurs-{circle.lower()}"
|
||||||
)
|
)
|
||||||
add_attendance_course_date(cs, attendance_course_lp_qs, circle, data)
|
|
||||||
elif language == "fr":
|
elif language == "fr":
|
||||||
# todo: this is a hack remove me
|
# todo: this is a hack remove me
|
||||||
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
|
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
|
||||||
slug=f"{course.slug}-lp-circle-véhicule-lc-cours-de-présence-véhicule-à-moteur"
|
slug=f"{course.slug}-lp-circle-véhicule-lc-cours-de-présence-véhicule-à-moteur"
|
||||||
)
|
)
|
||||||
add_attendance_course_date(cs, attendance_course_lp_qs, circle, data)
|
|
||||||
elif language == "it":
|
elif language == "it":
|
||||||
# todo: this is a hack remove me
|
# todo: this is a hack remove me
|
||||||
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
|
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
|
||||||
slug=f"{course.slug}-lp-circle-veicolo-lc-corso-di-presenza-veicolo"
|
slug=f"{course.slug}-lp-circle-veicolo-lc-corso-di-presenza-veicolo"
|
||||||
)
|
)
|
||||||
print(attendance_course_lp_qs)
|
|
||||||
add_attendance_course_date(cs, attendance_course_lp_qs, circle, data)
|
if attendance_course_lp_qs and attendance_course_lp_qs.exists():
|
||||||
|
csa = CourseSessionAttendanceCourse.objects.create(
|
||||||
|
course_session=cs,
|
||||||
|
learning_content=attendance_course_lp_qs.first(),
|
||||||
|
location=data[f"{circle} Raum"],
|
||||||
|
trainer="",
|
||||||
|
)
|
||||||
|
csa.due_date.start = try_parse_datetime(data[f"{circle} Start"])[1]
|
||||||
|
csa.due_date.end = try_parse_datetime(data[f"{circle} Ende"])[1]
|
||||||
|
csa.due_date.save()
|
||||||
|
|
||||||
return cs
|
return cs
|
||||||
|
|
||||||
|
|
||||||
def add_attendance_course_date(course_session, attendance_course_lp_qs, circle, data):
|
|
||||||
if attendance_course_lp_qs.exists():
|
|
||||||
course_session.attendance_courses.append(
|
|
||||||
{
|
|
||||||
"learningContentId": attendance_course_lp_qs.first().id,
|
|
||||||
"start": try_parse_datetime(data[f"{circle} Start"])[1].isoformat(),
|
|
||||||
"end": try_parse_datetime(data[f"{circle} Ende"])[1].isoformat(),
|
|
||||||
"location": data[f"{circle} Raum"],
|
|
||||||
"trainer": "",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
course_session.save()
|
|
||||||
|
|
||||||
|
|
||||||
def import_trainers_from_excel(course: Course, filename: str, language="de"):
|
def import_trainers_from_excel(course: Course, filename: str, language="de"):
|
||||||
workbook = load_workbook(filename=filename)
|
workbook = load_workbook(filename=filename)
|
||||||
sheet = workbook["Schulungen Trainer"]
|
sheet = workbook["Schulungen Trainer"]
|
||||||
|
|
@ -157,6 +153,7 @@ def import_trainers_from_excel(course: Course, filename: str, language="de"):
|
||||||
def create_or_update_trainer(course: Course, data: Dict[str, Any], language="de"):
|
def create_or_update_trainer(course: Course, data: Dict[str, Any], language="de"):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"create_or_update_trainer",
|
"create_or_update_trainer",
|
||||||
|
course=course.title,
|
||||||
data=data,
|
data=data,
|
||||||
label="import",
|
label="import",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from openpyxl.reader.excel import load_workbook
|
||||||
|
|
||||||
from vbv_lernwelt.course.creators.test_course import create_test_course
|
from vbv_lernwelt.course.creators.test_course import create_test_course
|
||||||
from vbv_lernwelt.course.models import CourseSession
|
from vbv_lernwelt.course.models import CourseSession
|
||||||
|
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
|
||||||
from vbv_lernwelt.importer.services import create_or_update_course_session
|
from vbv_lernwelt.importer.services import create_or_update_course_session
|
||||||
from vbv_lernwelt.importer.utils import calc_header_tuple_list_from_pyxl_sheet
|
from vbv_lernwelt.importer.utils import calc_header_tuple_list_from_pyxl_sheet
|
||||||
|
|
||||||
|
|
@ -61,20 +62,12 @@ class CreateOrUpdateCourseSessionTestCase(TestCase):
|
||||||
self.assertEqual(cs.region, "Deutschschweiz")
|
self.assertEqual(cs.region, "Deutschschweiz")
|
||||||
self.assertEqual(cs.group, "A")
|
self.assertEqual(cs.group, "A")
|
||||||
|
|
||||||
attendance_course = cs.attendance_courses[0]
|
attendance_course = CourseSessionAttendanceCourse.objects.first()
|
||||||
attendance_course = {
|
self.assertEqual(
|
||||||
k: v
|
attendance_course.due_date.start.isoformat(), "2023-06-06T11:30:00+00:00"
|
||||||
for k, v in attendance_course.items()
|
)
|
||||||
if k not in ["learningContentId", "location"]
|
self.assertEqual(
|
||||||
}
|
attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00"
|
||||||
|
|
||||||
self.assertDictEqual(
|
|
||||||
attendance_course,
|
|
||||||
{
|
|
||||||
"start": "2023-06-06T13:30:00",
|
|
||||||
"end": "2023-06-06T15:00:00",
|
|
||||||
"trainer": "",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_update_course_session(self):
|
def test_update_course_session(self):
|
||||||
|
|
@ -112,18 +105,10 @@ class CreateOrUpdateCourseSessionTestCase(TestCase):
|
||||||
self.assertEqual(cs.region, "Deutschschweiz")
|
self.assertEqual(cs.region, "Deutschschweiz")
|
||||||
self.assertEqual(cs.group, "A")
|
self.assertEqual(cs.group, "A")
|
||||||
|
|
||||||
attendance_course = cs.attendance_courses[0]
|
attendance_course = CourseSessionAttendanceCourse.objects.first()
|
||||||
attendance_course = {
|
self.assertEqual(
|
||||||
k: v
|
attendance_course.due_date.start.isoformat(), "2023-06-06T11:30:00+00:00"
|
||||||
for k, v in attendance_course.items()
|
)
|
||||||
if k not in ["learningContentId", "location"]
|
self.assertEqual(
|
||||||
}
|
attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00"
|
||||||
|
|
||||||
self.assertDictEqual(
|
|
||||||
attendance_course,
|
|
||||||
{
|
|
||||||
"start": "2023-06-06T13:30:00",
|
|
||||||
"end": "2023-06-06T15:00:00",
|
|
||||||
"trainer": "",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-06-28 11:21
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("assignment", "0004_assignment_assignment_type"),
|
||||||
|
("learnpath", "0007_learningunit_title_hidden"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="learningcontentassignment",
|
||||||
|
name="content_assignment",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT, to="assignment.assignment"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -279,6 +279,10 @@ class LearningContent(CourseBasePage):
|
||||||
|
|
||||||
|
|
||||||
class LearningContentAttendanceCourse(LearningContent):
|
class LearningContentAttendanceCourse(LearningContent):
|
||||||
|
"""
|
||||||
|
Präsenzkurs
|
||||||
|
"""
|
||||||
|
|
||||||
parent_page_types = ["learnpath.Circle"]
|
parent_page_types = ["learnpath.Circle"]
|
||||||
subpage_types = []
|
subpage_types = []
|
||||||
|
|
||||||
|
|
@ -346,7 +350,6 @@ class LearningContentAssignment(LearningContent):
|
||||||
content_assignment = models.ForeignKey(
|
content_assignment = models.ForeignKey(
|
||||||
"assignment.Assignment",
|
"assignment.Assignment",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name="+",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assignment_type = models.CharField(
|
assignment_type = models.CharField(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-07-12 07:05
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import jsonfield.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("contenttypes", "0002_remove_content_type_name"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("notify", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="notification",
|
||||||
|
options={
|
||||||
|
"ordering": ["-timestamp"],
|
||||||
|
"verbose_name": "Notification",
|
||||||
|
"verbose_name_plural": "Notifications",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="action_object_content_type",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="notify_action_object",
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
verbose_name="action object content type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="action_object_object_id",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="action object object id",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="actor_content_type",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="notify_actor",
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
verbose_name="actor content type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="actor_object_id",
|
||||||
|
field=models.CharField(max_length=255, verbose_name="actor object id"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="data",
|
||||||
|
field=jsonfield.fields.JSONField(
|
||||||
|
blank=True, null=True, verbose_name="data"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="deleted",
|
||||||
|
field=models.BooleanField(
|
||||||
|
db_index=True, default=False, verbose_name="deleted"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="description",
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name="description"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="emailed",
|
||||||
|
field=models.BooleanField(
|
||||||
|
db_index=True, default=False, verbose_name="emailed"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="level",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("success", "success"),
|
||||||
|
("info", "info"),
|
||||||
|
("warning", "warning"),
|
||||||
|
("error", "error"),
|
||||||
|
],
|
||||||
|
default="info",
|
||||||
|
max_length=20,
|
||||||
|
verbose_name="level",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="public",
|
||||||
|
field=models.BooleanField(
|
||||||
|
db_index=True, default=True, verbose_name="public"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="recipient",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="notifications",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="recipient",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="target_content_type",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="notify_target",
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
verbose_name="target content type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="target_object_id",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True, max_length=255, null=True, verbose_name="target object id"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="timestamp",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
db_index=True,
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
verbose_name="timestamp",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="unread",
|
||||||
|
field=models.BooleanField(
|
||||||
|
db_index=True, default=True, verbose_name="unread"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="verb",
|
||||||
|
field=models.CharField(max_length=255, verbose_name="verb"),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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="m23.71,4.86h-2.86v-1.53c0-.41-.34-.75-.75-.75s-.75.34-.75.75v1.53h-8.71v-1.53c0-.41-.34-.75-.75-.75s-.75.34-.75.75v1.53h-2.86c-1.24,0-2.25,1.01-2.25,2.25v15.77c0,1.24,1.01,2.25,2.25,2.25h17.43c1.24,0,2.25-1.01,2.25-2.25V7.11c0-1.24-1.01-2.25-2.25-2.25Zm-17.43,1.5h17.43c.41,0,.75.34.75.75v2.21H5.54v-2.21c0-.41.34-.75.75-.75Zm17.43,17.27H6.29c-.41,0-.75-.34-.75-.75v-12.06h18.93v12.06c0,.41-.34.75-.75.75Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 550 B |
Loading…
Reference in New Issue