feat: generic task rendering
This commit is contained in:
parent
b319b539fe
commit
e87c5a7cd8
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { RouteLocationRaw } from "vue-router";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
taskTitle: string;
|
||||||
|
circleTitle: string;
|
||||||
|
pendingTasks: number;
|
||||||
|
pendingTasksLabel: string;
|
||||||
|
taskLink: RouteLocationRaw;
|
||||||
|
taskLinkLabel: string;
|
||||||
|
taskLinkPendingLabel: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="flex flex-col items-start justify-between gap-4 border-b py-2 pl-5 pr-5 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
|
||||||
|
>
|
||||||
|
<!-- Left -->
|
||||||
|
<div class="flex flex-grow flex-row items-center justify-start">
|
||||||
|
<div class="w-80">
|
||||||
|
<div class="font-bold">{{ taskTitle }}</div>
|
||||||
|
<div class="text-small text-gray-900">
|
||||||
|
{{ $t("a.Circle") }} «{{ circleTitle }}»
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Center -->
|
||||||
|
<div class="flex flex-grow flex-row items-center justify-start space-x-2 pl-20">
|
||||||
|
<template v-if="pendingTasks > 0">
|
||||||
|
<div
|
||||||
|
class="flex h-7 w-7 items-center justify-center rounded-full border-2 border-green-500 px-3 py-1 text-sm font-bold"
|
||||||
|
>
|
||||||
|
<span>{{ pendingTasks }}</span>
|
||||||
|
</div>
|
||||||
|
<span>{{ pendingTasksLabel }}</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<!-- Right -->
|
||||||
|
<router-link
|
||||||
|
:class="[pendingTasks > 0 ? 'btn-primary' : 'underline']"
|
||||||
|
:to="taskLink"
|
||||||
|
>
|
||||||
|
<template v-if="pendingTasks > 0">{{ taskLinkPendingLabel }}</template>
|
||||||
|
<template v-else>{{ taskLinkLabel }}</template>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import AssignmentItem from "@/components/cockpit/mentor/AssignmentItem.vue";
|
||||||
|
import type { RouteLocationRaw } from "vue-router";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
taskTitle: string;
|
||||||
|
circleTitle: string;
|
||||||
|
pendingTasks: number;
|
||||||
|
taskLink: RouteLocationRaw;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AssignmentItem
|
||||||
|
:task-title="`Praxisauftrag: ${taskTitle}`"
|
||||||
|
:circle-title="circleTitle"
|
||||||
|
:pending-tasks="pendingTasks"
|
||||||
|
:task-link="taskLink"
|
||||||
|
pending-tasks-label="Ergebnisse abgegeben"
|
||||||
|
task-link-pending-label="Ergebnisse bewerten"
|
||||||
|
task-link-label="Praxisaufträge anschauen"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
@ -4,20 +4,12 @@ import { useMentorCockpit } from "@/services/mentorCockpit";
|
||||||
import { useCurrentCourseSession } from "@/composables";
|
import { useCurrentCourseSession } from "@/composables";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import { computed, type Ref, ref } from "vue";
|
import { computed, type Ref, ref } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import PraxisAssignmentItem from "@/components/cockpit/mentor/PraxisAssignmentItem.vue";
|
||||||
|
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
|
|
||||||
const mentorCockpitStore = useMentorCockpit(courseSession.value.id);
|
const mentorCockpitStore = useMentorCockpit(courseSession.value.id);
|
||||||
const summary = mentorCockpitStore.summary;
|
const summary = mentorCockpitStore.summary;
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const goToPraxisAssignment = (praxisAssignmentId: string) => {
|
|
||||||
router.push({
|
|
||||||
name: "mentorCockpitPraxisAssignments",
|
|
||||||
params: { praxisAssignmentId },
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const statusFilterValue = ref({ name: "Alle", id: "_all" });
|
const statusFilterValue = ref({ name: "Alle", id: "_all" });
|
||||||
|
|
||||||
|
|
@ -40,10 +32,10 @@ const circleFilter = computed(() => {
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredPraxisAssignments: Ref<PraxisAssignment[]> = computed(() => {
|
const filteredAssignments: Ref<PraxisAssignment[]> = computed(() => {
|
||||||
if (!summary.value) return [];
|
if (!summary.value) return [];
|
||||||
|
|
||||||
let filtered = summary.value.praxis_assignments;
|
let filtered = summary.value.assignments;
|
||||||
|
|
||||||
if (statusFilterValue.value.id !== "_all") {
|
if (statusFilterValue.value.id !== "_all") {
|
||||||
filtered = filtered.filter((item) => item.pending_evaluations > 0);
|
filtered = filtered.filter((item) => item.pending_evaluations > 0);
|
||||||
|
|
@ -74,64 +66,18 @@ const filteredPraxisAssignments: Ref<PraxisAssignment[]> = computed(() => {
|
||||||
:items="circleFilter"
|
:items="circleFilter"
|
||||||
borderless
|
borderless
|
||||||
></ItDropdownSelect>
|
></ItDropdownSelect>
|
||||||
<!-- <ItDropdownSelect-->
|
|
||||||
<!-- v-model="circleFilterValue"-->
|
|
||||||
<!-- class="min-w-[18rem]"-->
|
|
||||||
<!-- :items="circleFilter"-->
|
|
||||||
<!-- borderless-->
|
|
||||||
<!-- ></ItDropdownSelect>-->
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="item in filteredPraxisAssignments"
|
|
||||||
:key="item.id"
|
|
||||||
class="flex flex-col items-start justify-between gap-4 border-b py-2 pl-5 pr-5 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
|
|
||||||
>
|
|
||||||
<!-- Left -->
|
|
||||||
<div class="flex flex-grow flex-row items-center justify-start">
|
|
||||||
<div class="w-80">
|
|
||||||
<div class="font-bold">Praxisauftrag: {{ item.title }}</div>
|
|
||||||
<div class="text-small text-gray-900">
|
|
||||||
{{ $t("a.Circle") }} «{{
|
|
||||||
mentorCockpitStore.getCircleTitleById(item.circle_id)
|
|
||||||
}}»
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Center -->
|
|
||||||
<div class="flex flex-grow flex-row items-center justify-start space-x-2 pl-20">
|
|
||||||
<template v-if="item.pending_evaluations > 0">
|
|
||||||
<div
|
|
||||||
class="flex h-7 w-7 items-center justify-center rounded-full border-2 border-green-500 px-3 py-1 text-sm font-bold"
|
|
||||||
>
|
|
||||||
<span>{{ item.pending_evaluations }}</span>
|
|
||||||
</div>
|
|
||||||
<span>Ergebnisse abgegeben</span>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<!-- Right -->
|
|
||||||
<div>
|
|
||||||
<template v-if="item.pending_evaluations > 0">
|
|
||||||
<button class="btn-primary text-sm" @click="goToPraxisAssignment(item.id)">
|
|
||||||
Ergebnisse bewerten
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<router-link
|
|
||||||
class="underline"
|
|
||||||
:to="{
|
|
||||||
name: 'mentorCockpitPraxisAssignments',
|
|
||||||
params: { praxisAssignmentId: item.id },
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
Praxisaufträge anschauen
|
|
||||||
</router-link>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <router-link :to=" {name:
|
|
||||||
'cockpitUserProfile', params: {userId: participant.id}}" class="underline">-->
|
|
||||||
<!-- {{ $t("a.Profil anzeigen") }}-->
|
|
||||||
<!-- </router-link>-->
|
|
||||||
</div>
|
</div>
|
||||||
|
<template v-for="item in filteredAssignments" :key="item.id">
|
||||||
|
<PraxisAssignmentItem
|
||||||
|
v-if="item.type === 'praxis_assignment'"
|
||||||
|
:circle-title="mentorCockpitStore.getCircleTitleById(item.circle_id)"
|
||||||
|
:pending-tasks="item.pending_evaluations"
|
||||||
|
:task-link="{
|
||||||
|
name: 'mentorCockpitPraxisAssignments',
|
||||||
|
params: { praxisAssignmentId: item.id },
|
||||||
|
}"
|
||||||
|
:task-title="item.title"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,13 @@ export interface PraxisAssignment {
|
||||||
circle_id: string;
|
circle_id: string;
|
||||||
pending_evaluations: number;
|
pending_evaluations: number;
|
||||||
completions: Completion[];
|
completions: Completion[];
|
||||||
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Summary {
|
interface Summary {
|
||||||
participants: Participant[];
|
participants: Participant[];
|
||||||
circles: Circle[];
|
circles: Circle[];
|
||||||
praxis_assignments: PraxisAssignment[];
|
assignments: PraxisAssignment[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMentorCockpit = (
|
export const useMentorCockpit = (
|
||||||
|
|
@ -59,8 +60,10 @@ export const useMentorCockpit = (
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPraxisAssignmentById = (id: string): PraxisAssignment | null => {
|
const getPraxisAssignmentById = (id: string): PraxisAssignment | null => {
|
||||||
if (summary.value?.praxis_assignments) {
|
if (summary.value?.assignments) {
|
||||||
const found = summary.value.praxis_assignments.find((pa) => pa.id === id);
|
const found = summary.value.assignments.find(
|
||||||
|
(assignment) => assignment.id === id
|
||||||
|
);
|
||||||
return found ? found : null;
|
return found ? found : null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class PraxisAssignmentStatusSerializer(serializers.Serializer):
|
||||||
circle_id = serializers.CharField()
|
circle_id = serializers.CharField()
|
||||||
pending_evaluations = serializers.IntegerField()
|
pending_evaluations = serializers.IntegerField()
|
||||||
completions = PraxisAssignmentCompletionSerializer(many=True)
|
completions = PraxisAssignmentCompletionSerializer(many=True)
|
||||||
|
type = serializers.ReadOnlyField(default="praxis_assignment")
|
||||||
|
|
||||||
|
|
||||||
class InvitationSerializer(serializers.ModelSerializer):
|
class InvitationSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ class LearningMentorAPITest(APITestCase):
|
||||||
# THEN
|
# THEN
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(response.data["participants"], [])
|
self.assertEqual(response.data["participants"], [])
|
||||||
self.assertEqual(response.data["praxis_assignments"], [])
|
self.assertEqual(response.data["assignments"], [])
|
||||||
|
|
||||||
def test_api_participants(self) -> None:
|
def test_api_participants(self) -> None:
|
||||||
# GIVEN
|
# GIVEN
|
||||||
|
|
@ -140,15 +140,17 @@ class LearningMentorAPITest(APITestCase):
|
||||||
response = self.client.get(self.url)
|
response = self.client.get(self.url)
|
||||||
|
|
||||||
# THEN
|
# THEN
|
||||||
|
assignments = response.data["assignments"]
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(len(response.data["praxis_assignments"]), 1)
|
self.assertEqual(len(assignments), 1)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response.data["circles"],
|
response.data["circles"],
|
||||||
[{"id": self.circle.id, "title": self.circle.title}],
|
[{"id": self.circle.id, "title": self.circle.title}],
|
||||||
)
|
)
|
||||||
|
|
||||||
assignment = response.data["praxis_assignments"][0]
|
assignment = assignments[0]
|
||||||
|
self.assertEqual(assignment["type"], "praxis_assignment")
|
||||||
self.assertEqual(assignment["pending_evaluations"], 1)
|
self.assertEqual(assignment["pending_evaluations"], 1)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
|
||||||
|
|
@ -34,19 +34,28 @@ def mentor_summary(request, course_session_id: int):
|
||||||
participants = mentor.participants.filter(course_session=course_session)
|
participants = mentor.participants.filter(course_session=course_session)
|
||||||
users = [p.user for p in participants]
|
users = [p.user for p in participants]
|
||||||
|
|
||||||
praxis_assignments, circle_ids = get_praxis_assignments(
|
assignments = []
|
||||||
|
circle_ids = set()
|
||||||
|
|
||||||
|
praxis_assignments, _circle_ids = get_praxis_assignments(
|
||||||
course_session=course_session, participants=users, evaluation_user=request.user
|
course_session=course_session, participants=users, evaluation_user=request.user
|
||||||
)
|
)
|
||||||
|
assignments.extend(
|
||||||
|
PraxisAssignmentStatusSerializer(praxis_assignments, many=True).data
|
||||||
|
)
|
||||||
|
circle_ids.update(_circle_ids)
|
||||||
|
|
||||||
circles = Circle.objects.filter(id__in=circle_ids).values("id", "title")
|
circles = Circle.objects.filter(id__in=circle_ids).values("id", "title")
|
||||||
|
|
||||||
|
assignments.sort(
|
||||||
|
key=lambda x: (-x.get("pending_evaluations", 0), x.get("title", "").lower())
|
||||||
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"participants": [UserSerializer(user).data for user in users],
|
"participants": [UserSerializer(user).data for user in users],
|
||||||
"circles": list(circles),
|
"circles": list(circles),
|
||||||
"praxis_assignments": PraxisAssignmentStatusSerializer(
|
"assignments": assignments,
|
||||||
praxis_assignments, many=True
|
|
||||||
).data,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue