Add frontend export
This commit is contained in:
parent
5b60e50ac4
commit
033886f00b
|
|
@ -3,21 +3,17 @@ import { computed, ref, watch } from "vue";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import type { StatisticsCourseSessionPropertiesType } from "@/gql/graphql";
|
import type { StatisticsCourseSessionPropertiesType } from "@/gql/graphql";
|
||||||
|
import type { StatisticsFilterItem } from "@/types";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
interface Item {
|
|
||||||
_id: string;
|
|
||||||
course_session_id: string;
|
|
||||||
generation: string;
|
|
||||||
circle_id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
items: Item[];
|
items: StatisticsFilterItem[];
|
||||||
courseSessionProperties: StatisticsCourseSessionPropertiesType;
|
courseSessionProperties: StatisticsCourseSessionPropertiesType;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
defineExpose({ getFilteredItems });
|
||||||
|
|
||||||
const sessionFilter = computed(() => {
|
const sessionFilter = computed(() => {
|
||||||
const f = props.courseSessionProperties.sessions.map((session) => ({
|
const f = props.courseSessionProperties.sessions.map((session) => ({
|
||||||
name: `${t("a.Durchfuehrung")}: ${session.name}`,
|
name: `${t("a.Durchfuehrung")}: ${session.name}`,
|
||||||
|
|
@ -71,6 +67,10 @@ const filteredItems = computed(() => {
|
||||||
return sessionMatch && generationMatch && circleMatch;
|
return sessionMatch && generationMatch && circleMatch;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getFilteredItems() {
|
||||||
|
return filteredItems.value;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue"
|
||||||
import { getDateString } from "@/components/dueDates/dueDatesUtils";
|
import { getDateString } from "@/components/dueDates/dueDatesUtils";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||||
|
import { type Ref, ref } from "vue";
|
||||||
|
import { exportDataAsXls } from "@/utils/export";
|
||||||
|
import { exportCompetenceElements } from "@/services/dashboard";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -17,6 +20,8 @@ const props = defineProps<{
|
||||||
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const statisticFilter: Ref<typeof StatisticFilterList | null> = ref(null);
|
||||||
|
|
||||||
const assignmentStats = (metrics: AssignmentCompletionMetricsType) => {
|
const assignmentStats = (metrics: AssignmentCompletionMetricsType) => {
|
||||||
if (!metrics.ranking_completed) {
|
if (!metrics.ranking_completed) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -36,15 +41,28 @@ const assignmentStats = (metrics: AssignmentCompletionMetricsType) => {
|
||||||
const total = (metrics: AssignmentCompletionMetricsType) => {
|
const total = (metrics: AssignmentCompletionMetricsType) => {
|
||||||
return metrics.passed_count + metrics.failed_count + metrics.unranked_count;
|
return metrics.passed_count + metrics.failed_count + metrics.unranked_count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function exportData() {
|
||||||
|
if (!statisticFilter.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filteredItems = statisticFilter.value.getFilteredItems();
|
||||||
|
await exportDataAsXls(filteredItems, exportCompetenceElements);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div class="mb-10 flex items-center justify-between">
|
<div class="mb-10 flex items-center justify-between">
|
||||||
<h3>{{ $t("a.Kompetenznachweis-Elemente") }}</h3>
|
<h3>{{ $t("a.Kompetenznachweis-Elemente") }}</h3>
|
||||||
|
<button class="flex" @click="exportData">
|
||||||
|
<it-icon-export></it-icon-export>
|
||||||
|
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="courseStatistics?.assignments.records" class="mt-8 bg-white">
|
<div v-if="courseStatistics?.assignments.records" class="mt-8 bg-white">
|
||||||
<StatisticFilterList
|
<StatisticFilterList
|
||||||
|
ref="statisticFilter"
|
||||||
:course-session-properties="courseStatistics?.course_session_properties"
|
:course-session-properties="courseStatistics?.course_session_properties"
|
||||||
:items="courseStatistics.assignments.records"
|
:items="courseStatistics.assignments.records"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue"
|
||||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||||
import { getDateString } from "@/components/dueDates/dueDatesUtils";
|
import { getDateString } from "@/components/dueDates/dueDatesUtils";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { ref, type Ref } from "vue";
|
||||||
|
import { exportDataAsXls } from "@/utils/export";
|
||||||
|
import { exportAttendance } from "@/services/dashboard";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -16,6 +19,8 @@ const props = defineProps<{
|
||||||
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const statisticFilter: Ref<typeof StatisticFilterList | null> = ref(null);
|
||||||
|
|
||||||
const attendanceStats = (present: number, total: number) => {
|
const attendanceStats = (present: number, total: number) => {
|
||||||
return {
|
return {
|
||||||
SUCCESS: present,
|
SUCCESS: present,
|
||||||
|
|
@ -23,18 +28,31 @@ const attendanceStats = (present: number, total: number) => {
|
||||||
UNKNOWN: 0,
|
UNKNOWN: 0,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function exportData() {
|
||||||
|
if (!statisticFilter.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filteredItems = statisticFilter.value.getFilteredItems();
|
||||||
|
await exportDataAsXls(filteredItems, exportAttendance);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div class="mb-10 flex items-center justify-between">
|
<div class="mb-10 flex items-center justify-between">
|
||||||
<h3>{{ $t("Anwesenheit") }}</h3>
|
<h3>{{ $t("Anwesenheit") }}</h3>
|
||||||
|
<button class="flex" @click="exportData">
|
||||||
|
<it-icon-export></it-icon-export>
|
||||||
|
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="courseStatistics?.attendance_day_presences.records"
|
v-if="courseStatistics?.attendance_day_presences.records"
|
||||||
class="mt-8 bg-white"
|
class="mt-8 bg-white"
|
||||||
>
|
>
|
||||||
<StatisticFilterList
|
<StatisticFilterList
|
||||||
|
ref="statisticFilter"
|
||||||
:course-session-properties="courseStatistics.course_session_properties"
|
:course-session-properties="courseStatistics.course_session_properties"
|
||||||
:items="courseStatistics.attendance_day_presences.records"
|
:items="courseStatistics.attendance_day_presences.records"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ import type {
|
||||||
} from "@/gql/graphql";
|
} from "@/gql/graphql";
|
||||||
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
||||||
import { getBlendedColorForRating } from "@/utils/ratingToColor";
|
import { getBlendedColorForRating } from "@/utils/ratingToColor";
|
||||||
|
import { ref, type Ref } from "vue";
|
||||||
|
import { exportDataAsXls } from "@/utils/export";
|
||||||
|
import { exportFeedback } from "@/services/dashboard";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -14,15 +17,30 @@ const props = defineProps<{
|
||||||
courseSessionName: (sessionId: string) => string;
|
courseSessionName: (sessionId: string) => string;
|
||||||
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
circleMeta: (circleId: string) => StatisticsCircleDataType;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const statisticFilter: Ref<typeof StatisticFilterList | null> = ref(null);
|
||||||
|
|
||||||
|
async function exportData() {
|
||||||
|
if (!statisticFilter.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filteredItems = statisticFilter.value.getFilteredItems();
|
||||||
|
await exportDataAsXls(filteredItems, exportFeedback);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div class="mb-10 flex items-center justify-between">
|
<div class="mb-10 flex items-center justify-between">
|
||||||
<h3>{{ $t("a.Feedback Teilnehmer") }}</h3>
|
<h3>{{ $t("a.Feedback Teilnehmer") }}</h3>
|
||||||
|
<button class="flex" @click="exportData">
|
||||||
|
<it-icon-export></it-icon-export>
|
||||||
|
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="courseStatistics?.feedback_responses.records" class="mt-8 bg-white">
|
<div v-if="courseStatistics?.feedback_responses.records" class="mt-8 bg-white">
|
||||||
<StatisticFilterList
|
<StatisticFilterList
|
||||||
|
ref="statisticFilter"
|
||||||
:course-session-properties="courseStatistics.course_session_properties"
|
:course-session-properties="courseStatistics.course_session_properties"
|
||||||
:items="courseStatistics.feedback_responses.records"
|
:items="courseStatistics.feedback_responses.records"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,12 @@ import type {
|
||||||
CourseStatisticsType,
|
CourseStatisticsType,
|
||||||
DashboardConfigType,
|
DashboardConfigType,
|
||||||
} from "@/gql/graphql";
|
} from "@/gql/graphql";
|
||||||
import type { DashboardPersonsPageMode, DueDate } from "@/types";
|
import type {
|
||||||
|
DashboardPersonsPageMode,
|
||||||
|
DueDate,
|
||||||
|
XlsExportRequestData,
|
||||||
|
XlsExportResponseData,
|
||||||
|
} from "@/types";
|
||||||
|
|
||||||
export type DashboardPersonRoleType =
|
export type DashboardPersonRoleType =
|
||||||
| "SUPERVISOR"
|
| "SUPERVISOR"
|
||||||
|
|
@ -189,25 +194,22 @@ export async function fetchOpenTasksCount(courseId: string) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportFeedback(data: {
|
export async function exportFeedback(
|
||||||
courseSessionIds: string[];
|
data: XlsExportRequestData
|
||||||
circleIds: string[];
|
): Promise<XlsExportResponseData> {
|
||||||
}) {
|
|
||||||
return await itPost("/api/dashboard/export/feedback/", data);
|
return await itPost("/api/dashboard/export/feedback/", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportAttendance(data: {
|
export async function exportAttendance(
|
||||||
courseSessionIds: string[];
|
data: XlsExportRequestData
|
||||||
circleIds: string[];
|
): Promise<XlsExportResponseData> {
|
||||||
}) {
|
|
||||||
return await itPost("/api/dashboard/export/attendance/", data);
|
return await itPost("/api/dashboard/export/attendance/", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportCertificate(data: {
|
export async function exportCompetenceElements(
|
||||||
courseSessionIds: string[];
|
data: XlsExportRequestData
|
||||||
circleIds: string[];
|
): Promise<XlsExportResponseData> {
|
||||||
}) {
|
return await itPost("/api/dashboard/export/competence_elements/", data);
|
||||||
return await itPost("/api/dashboard/export/certificate/", data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function courseIdForCourseSlug(
|
export function courseIdForCourseSlug(
|
||||||
|
|
|
||||||
|
|
@ -618,3 +618,20 @@ export type User = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DashboardPersonsPageMode = "default" | "competenceMetrics";
|
export type DashboardPersonsPageMode = "default" | "competenceMetrics";
|
||||||
|
|
||||||
|
export interface StatisticsFilterItem {
|
||||||
|
_id: string;
|
||||||
|
course_session_id: string;
|
||||||
|
generation: string;
|
||||||
|
circle_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XlsExportRequestData {
|
||||||
|
courseSessionIds: number[];
|
||||||
|
circleIds: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XlsExportResponseData {
|
||||||
|
encoded_data: string;
|
||||||
|
file_name: string;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,58 @@
|
||||||
/**
|
import type {
|
||||||
* Created by christiancueni on 17.06.2024.
|
StatisticsFilterItem,
|
||||||
* Copyright (c) 2024 ITerativ GmbH. All rights reserved.
|
XlsExportRequestData,
|
||||||
*/
|
XlsExportResponseData,
|
||||||
|
} from "@/types";
|
||||||
|
|
||||||
|
interface exportApiCall {
|
||||||
|
(data: XlsExportRequestData): Promise<XlsExportResponseData>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function exportDataAsXls(
|
||||||
|
items: StatisticsFilterItem[],
|
||||||
|
apiCall: exportApiCall
|
||||||
|
) {
|
||||||
|
const itemIds = extractUniqueIds(items);
|
||||||
|
const data = await apiCall(itemIds);
|
||||||
|
openDataAsXls(data.encoded_data, data.file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openDataAsXls(encodedData: string, filename: string) {
|
||||||
|
// Decode base64 string to binary data
|
||||||
|
const byteCharacters = atob(encodedData);
|
||||||
|
const byteNumbers = new Array(byteCharacters.length);
|
||||||
|
for (let i = 0; i < byteCharacters.length; i++) {
|
||||||
|
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||||
|
}
|
||||||
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
|
const blob = new Blob([byteArray], { type: "application/octet-stream" });
|
||||||
|
|
||||||
|
// Create a link element and trigger download
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to extract unique circle_ids and course_session_ids
|
||||||
|
export function extractUniqueIds(data: StatisticsFilterItem[]): {
|
||||||
|
circleIds: number[];
|
||||||
|
courseSessionIds: number[];
|
||||||
|
} {
|
||||||
|
const circleIdsSet = new Set<number>();
|
||||||
|
const courseSessionIdsSet = new Set<number>();
|
||||||
|
|
||||||
|
data.forEach((item) => {
|
||||||
|
circleIdsSet.add(Number(item.circle_id));
|
||||||
|
courseSessionIdsSet.add(Number(item.course_session_id));
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
circleIds: Array.from(circleIdsSet),
|
||||||
|
courseSessionIds: Array.from(courseSessionIdsSet),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,8 @@ urlpatterns = [
|
||||||
path(r"api/dashboard/course/<str:course_id>/open_tasks/", get_mentor_open_tasks_count,
|
path(r"api/dashboard/course/<str:course_id>/open_tasks/", get_mentor_open_tasks_count,
|
||||||
name="get_mentor_open_tasks_count"),
|
name="get_mentor_open_tasks_count"),
|
||||||
path(r"api/dashboard/export/attendance/", export_attendance_as_xsl, name="export_attendance_as_xsl"),
|
path(r"api/dashboard/export/attendance/", export_attendance_as_xsl, name="export_attendance_as_xsl"),
|
||||||
path(r"api/dashboard/export/certificate/", export_competence_elements_as_xsl, name="export_certificate_as_xsl"),
|
path(r"api/dashboard/export/competence_elements/", export_competence_elements_as_xsl,
|
||||||
|
name="export_certificate_as_xsl"),
|
||||||
path(r"api/dashboard/export/feedback/", export_feedback_as_xsl, name="export_feedback_as_xsl"),
|
path(r"api/dashboard/export/feedback/", export_feedback_as_xsl, name="export_feedback_as_xsl"),
|
||||||
|
|
||||||
# course
|
# course
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue