vbv/client/src/pages/dashboard/DashboardPersonsPage.vue

541 lines
16 KiB
Vue

<script setup lang="ts">
import { useChosenProfileMapping } from "@/components/dashboard/composables";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import LoadingSpinner from "@/components/ui/LoadingSpinner.vue";
import { useDashboardPersonsDueDates } from "@/composables";
import {
type DashboardPersonCourseSessionType,
exportPersons,
} from "@/services/dashboard";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useUserStore } from "@/stores/user";
import type { DashboardPersonsPageMode } from "@/types";
import { openDataAsXls } from "@/utils/export";
import { useRouteQuery } from "@vueuse/router";
import dayjs from "dayjs";
import { useTranslation } from "i18next-vue";
import _ from "lodash";
import log from "loglevel";
import { computed, ref, watch } from "vue";
log.debug("DashboardPersonsPage created");
export interface Props {
mode?: DashboardPersonsPageMode;
}
const props = withDefaults(defineProps<Props>(), {
mode: "default",
});
const UNFILTERED = Number.MAX_SAFE_INTEGER.toString();
type MenuItem = {
id: string;
name: string;
};
const { t } = useTranslation();
const userStore = useUserStore();
const { loading, dashboardPersons } = useDashboardPersonsDueDates(props.mode);
const { CHOSEN_PROFILE_TO_NAME } = useChosenProfileMapping();
const { allCourseSessionActions } = useCourseSessionsStore();
const courses = computed(() => {
return [
{
id: UNFILTERED,
name: `${t("Lehrgang")}: ${t("a.Alle")}`,
},
..._(dashboardPersons.value)
.flatMap((person) => person.course_sessions)
.map((cs) => {
return { name: cs.course_title, id: cs.course_id };
})
.uniqBy("id")
.orderBy("name")
.value(),
];
});
const selectedCourse = ref<MenuItem>(courses.value[0]);
const selectedCourseRouteQuery = useRouteQuery("course", UNFILTERED, {
mode: "replace",
});
watch(selectedCourse, () => {
selectedCourseRouteQuery.value = selectedCourse.value.id;
});
watch(courses, () => {
if (selectedCourseRouteQuery.value !== UNFILTERED) {
selectedCourse.value =
courses.value.find((course) => course.id === selectedCourseRouteQuery.value) ||
courses.value[0];
}
});
const regions = computed(() => {
let values = _(dashboardPersons.value)
.flatMap((person) => person.course_sessions)
.map((cs) => {
return Object.assign({}, cs, { name: cs.region, id: cs.region });
})
.filter((cs) => !!cs.region)
.uniqBy("id")
.orderBy("name")
.value();
// filter by selected course
if (selectedCourse.value.id !== UNFILTERED) {
values = values.filter((cs) => cs.course_id === selectedCourse.value.id);
}
return [
{
id: UNFILTERED,
name: `${t("a.Region")}: ${t("a.Alle")}`,
},
...values,
];
});
const selectedRegion = ref<MenuItem>(regions.value[0]);
const courseSessions = computed(() => {
let values = _(dashboardPersons.value)
.flatMap((person) => person.course_sessions)
.map((cs) => {
return Object.assign({}, cs, { name: cs.session_title, id: cs.id });
})
.uniqBy("id")
.orderBy("name")
.value();
// filter by selected course
if (selectedCourse.value.id !== UNFILTERED) {
values = values.filter((cs) => cs.course_id === selectedCourse.value.id);
}
// filter by selected region
if (selectedRegion.value.id !== UNFILTERED) {
values = values.filter((cs) => cs.region === selectedRegion.value.id);
}
return [
{
id: UNFILTERED,
name: `${t("Durchführung")}: ${t("a.Alle")}`,
},
...values,
];
});
const selectedSession = ref<MenuItem>(courseSessions.value[0]);
const generations = computed(() => {
const values = _(dashboardPersons.value)
.flatMap((person) => person.course_sessions)
.map((cs) => {
return Object.assign({}, cs, { name: cs.generation, id: cs.generation });
})
.filter((cs) => !!cs.generation)
.uniqBy("id")
.orderBy("name")
.value();
return [
{
id: UNFILTERED,
name: `${t("a.Generation")}: ${t("a.Alle")}`,
},
...values,
];
});
const selectedGeneration = ref<MenuItem>(generations.value[0]);
const roles = computed(() => {
const values = _(dashboardPersons.value)
.flatMap((person) => person.course_sessions)
.map((cs) => {
return Object.assign({}, cs, {
name: cs.user_role_display,
id: cs.user_role_display,
});
})
.uniqBy("id")
.orderBy("name")
.value();
return [
{
id: UNFILTERED,
name: `${t("a.Rolle")}: ${t("a.Alle")}`,
},
...values,
];
});
const selectedRole = ref<MenuItem>(roles.value[0]);
const chosenProfiles = computed(() => {
const values = _(dashboardPersons.value)
.map((cs) => {
return Object.assign({}, cs, {
name: CHOSEN_PROFILE_TO_NAME[cs.chosen_profile],
id: cs.chosen_profile,
});
})
.uniqBy("id")
.orderBy("name")
.value();
return [
{
id: UNFILTERED,
name: `${t("a.Zulassungsprofil")}: ${t("a.Alle")}`,
},
...values,
];
});
const selectedChosenProfile = ref<MenuItem>(chosenProfiles.value[0]);
const paidYears = computed(() => {
const values = _(dashboardPersons.value)
.filter((cs) => dayjs(cs.paid_datetime).isValid())
.map((cs) => {
const paidYear = dayjs(cs.paid_datetime).format("YYYY");
return Object.assign({}, cs, {
name: paidYear,
id: paidYear,
});
})
.uniqBy("id")
.orderBy("name")
.value();
return [
{
id: UNFILTERED,
name: `${t("a.Jahr")}: ${t("a.Alle")}`,
},
...values,
];
});
const selectedPaidYear = ref<MenuItem>(paidYears.value[0]);
const selectedPaidYearQuery = useRouteQuery("selectedPaidYear", UNFILTERED, {
mode: "replace",
});
watch(selectedPaidYear, () => {
selectedPaidYearQuery.value = selectedPaidYear.value.id;
});
watch(paidYears, () => {
if (selectedPaidYearQuery.value !== UNFILTERED) {
selectedPaidYear.value =
paidYears.value.find((paidYear) => paidYear.id === selectedPaidYearQuery.value) ||
paidYears.value[0];
}
});
const filteredPersons = computed(() => {
return _.orderBy(
dashboardPersons.value
.filter((person) => {
if (selectedCourse.value.id === UNFILTERED) {
return true;
}
return person.course_sessions.some(
(cs) => cs.course_id === selectedCourse.value.id
);
})
.filter((person) => {
if (selectedSession.value.id === UNFILTERED) {
return true;
}
return person.course_sessions.some((cs) => cs.id === selectedSession.value.id);
})
.filter((person) => {
if (selectedRegion.value.id === UNFILTERED) {
return true;
}
return person.course_sessions.some(
(cs) => cs.region === selectedRegion.value.id
);
})
.filter((person) => {
if (selectedGeneration.value.id === UNFILTERED) {
return true;
}
return person.course_sessions.some(
(cs) => cs.generation === selectedGeneration.value.id
);
})
.filter((person) => {
if (selectedRole.value.id === UNFILTERED) {
return true;
}
return person.course_sessions.some(
(cs) => cs.user_role_display === selectedRole.value.id
);
})
.filter((person) => {
if (selectedChosenProfile.value.id === UNFILTERED) {
return true;
}
return person.chosen_profile === selectedChosenProfile.value.id;
})
.filter((person) => {
if (selectedPaidYear.value.id === UNFILTERED) {
return true;
}
const paidYear = dayjs(person.paid_datetime).format("YYYY");
return paidYear == selectedPaidYear.value.id;
}),
["last_name", "first_name"]
);
});
const filtersVisible = computed(() => {
return (
courses.value.length > 2 ||
courseSessions.value.length > 2 ||
regions.value.length > 2 ||
generations.value.length > 2 ||
roles.value.length > 2 ||
chosenProfiles.value.length > 2 ||
paidYears.value.length > 2
);
});
const canExport = computed(() => {
// If user is only member, he can't export
// member = has is_member property, but not is_expert or is_supervisor
if (!allCourseSessionActions.has("is_member")) {
// is berufsbildner or expert or supervisor
return true;
}
if (
allCourseSessionActions.has("is_expert") ||
allCourseSessionActions.has("is_supervisor")
) {
// is member
return true;
}
return false;
});
function personRoleDisplayValue(personCourseSession: DashboardPersonCourseSessionType) {
if (
[
"SUPERVISOR",
"EXPERT",
"LEARNING_MENTOR",
"BERUFSBILDNER",
"LEARNING_MENTOR",
].includes(personCourseSession.user_role)
) {
return personCourseSession.user_role_display;
}
return "";
}
async function exportData() {
const requestData = {
courseSessionUserIds: filteredPersons.value.map((person) => person.csu_id),
};
const data = await exportPersons(requestData, userStore.language);
openDataAsXls(data.encoded_data, data.file_name);
}
watch(selectedCourse, () => {
selectedRegion.value = regions.value[0];
});
watch(selectedRegion, () => {
selectedSession.value = courseSessions.value[0];
selectedGeneration.value = generations.value[0];
selectedRole.value = roles.value[0];
});
</script>
<template>
<div>
<div v-if="loading" class="m-8 flex justify-center">
<LoadingSpinner />
</div>
<div v-else class="bg-gray-200">
<div class="container-large">
<router-link
:to="`/`"
class="btn-text inline-flex items-center p-0"
data-cy="back-to-learning-path-button"
>
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
<span class="inline">{{ $t("general.back") }}</span>
</router-link>
<div class="mb-10 flex items-center justify-between">
<h2 class="my-4">{{ $t("a.Personen") }}</h2>
<button
v-if="canExport"
class="flex"
data-cy="export-button"
@click="exportData"
>
<it-icon-export></it-icon-export>
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
</button>
</div>
<div class="bg-white px-4 py-2">
<section
v-if="filtersVisible"
class="flex flex-col space-x-0 border-b bg-white lg:flex-row lg:space-x-3"
>
<ItDropdownSelect
v-if="courses.length > 2"
v-model="selectedCourse"
data-cy="select-course"
:items="courses"
borderless
></ItDropdownSelect>
<ItDropdownSelect
v-if="regions.length > 2"
v-model="selectedRegion"
data-cy="select-region"
:items="regions"
borderless
></ItDropdownSelect>
<ItDropdownSelect
v-if="courseSessions.length > 2"
v-model="selectedSession"
data-cy="select-course"
:items="courseSessions"
borderless
></ItDropdownSelect>
<ItDropdownSelect
v-if="generations.length > 2"
v-model="selectedGeneration"
data-cy="select-generation"
:items="generations"
borderless
></ItDropdownSelect>
<ItDropdownSelect
v-if="roles.length > 2"
v-model="selectedRole"
data-cy="select-role"
:items="roles"
borderless
></ItDropdownSelect>
<ItDropdownSelect
v-if="chosenProfiles.length > 2"
v-model="selectedChosenProfile"
data-cy="select-chosen-profile"
:items="chosenProfiles"
borderless
></ItDropdownSelect>
<ItDropdownSelect
v-if="paidYears.length > 2"
v-model="selectedPaidYear"
data-cy="select-paid-year"
:items="paidYears"
borderless
></ItDropdownSelect>
</section>
<div
v-for="person in filteredPersons"
:key="person.user_id"
data-cy="person"
class="flex flex-col justify-between gap-4 border-b p-2 last:border-b-0 md:flex-row md:items-center md:justify-between md:gap-16"
>
<div class="w-full flex-auto md:w-1/3">
<div class="flex items-center space-x-2">
<img
class="inline-block h-11 w-11 rounded-full"
:src="
person.avatar_url_small ||
'/static/avatars/myvbv-default-avatar.png'
"
:alt="`${person.first_name} ${person.last_name}`"
/>
<div>
<div class="text-bold">
{{ person.first_name }}
{{ person.last_name }}
</div>
<div class="text-gray-900">{{ person.email }}</div>
</div>
</div>
</div>
<div class="w-full flex-auto items-start md:w-2/3">
<div
v-for="cs in person.course_sessions"
:key="cs.id"
class="w-full border-b pb-2 pt-2 first:pt-0 last:border-b-0 last:pb-0"
>
<div class="flex flex-col md:flex-row md:items-center">
<div v-if="props.mode === 'default'" class="md:w-1/2">
<div class="text-gray-900">{{ cs.course_title }}</div>
<div v-if="cs.is_uk">{{ cs.session_title }}</div>
</div>
<div v-if="props.mode === 'competenceMetrics'" class="md:w-1/3">
<div
v-if="cs.competence_metrics?.passed_count || 0 > 0"
class="my-2 w-fit rounded-md bg-green-200 px-2.5 py-0.5"
>
{{ $t("a.Bestanden") }}:
{{ cs.competence_metrics?.passed_count }}
</div>
</div>
<div v-if="props.mode === 'default'" class="md:w-1/4">
{{ personRoleDisplayValue(cs) }}
</div>
<div v-else-if="props.mode === 'competenceMetrics'" class="md:w-1/3">
<div
v-if="cs.competence_metrics?.failed_count || 0 > 0"
class="my-2 w-fit rounded-md bg-error-red-200 px-2.5 py-0.5"
>
{{ $t("a.Nicht bestanden") }}:
{{ cs.competence_metrics?.failed_count }}
</div>
</div>
<div class="md:w-1/4 md:text-right">
<div
v-if="
(['SUPERVISOR', 'EXPERT'].includes(cs.my_role) &&
cs.user_role === 'MEMBER') ||
(['LEARNING_MENTOR', 'BERUFSBILDNER'].includes(cs.my_role) &&
cs.user_role === 'PARTICIPANT')
"
>
<router-link
:to="{
name: 'profileLearningPath',
params: {
userId: person.user_id,
courseSlug: cs.course_slug,
},
query: { courseSessionId: cs.id },
}"
data-cy="person-learning-path-link"
class="link w-full lg:text-right"
>
{{ $t("a.Profil anzeigen") }}
</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>