Add HorizontalBar component

This commit is contained in:
Christian Cueni 2023-01-30 09:36:29 +01:00
parent b7038c1a9c
commit 2ab8f580bc
5 changed files with 72 additions and 44 deletions

View File

@ -1,24 +1,42 @@
<template> <template>
<QuestionSummary :title="props.title" :text="props.text"> <QuestionSummary :title="props.title" :text="props.text">
<h5 class="mb-6 text-base">{{ props.items.length }} {{ $t("feedback.answers") }}</h5>
<div <div
v-for="{ label, percentage } in props.items" v-for="{ label, percentage } in chartItems"
:key="label" :key="label"
class="mb-6 flex flex-row flex-wrap items-center gap-3 gap-y-2" class="mb-6 flex flex-row flex-wrap items-center gap-3 gap-y-2"
> >
<div class="w-full text-base font-bold">{{ label }}</div> <Popover class="relative w-full">
<PopoverButton class="focus:outline-none">
<div class="w-full text-base font-bold">{{ label }}</div>
</PopoverButton>
<PopoverPanel
class="absolute top-[-200%] z-10 w-[120px] border border-gray-500 bg-white p-1 text-left text-sm font-normal"
>
<p>
{{
`"${label}" ${percentage * props.items.length} ${$t(
"feedback.answers"
)}`
}}
</p>
</PopoverPanel>
</Popover>
<div <div
class="h-8 bg-sky-500" class="h-8 bg-sky-500"
:style="{ width: `${percentage * 100 * 0.8}%` }" :style="{ width: `${percentage * 100 * 0.8}%` }"
></div> ></div>
<div class="text-sm">{{ percentage * 100 }}%</div> <div class="text-sm">{{ (percentage * 100).toFixed(1) }}%</div>
</div> </div>
</QuestionSummary> </QuestionSummary>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import QuestionSummary from "@/components/ui/QuestionSummary.vue"; import QuestionSummary from "@/components/ui/QuestionSummary.vue";
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
import { computed } from "vue";
export interface ChartItem { interface ChartItem {
percentage: number; percentage: number;
label: string; label: string;
} }
@ -26,6 +44,29 @@ export interface ChartItem {
const props = defineProps<{ const props = defineProps<{
title: string; title: string;
text: string; text: string;
items: ChartItem[]; items: string[];
}>(); }>();
const chartItems = computed<ChartItem[]>(() => {
const chartItems = props.items.reduce((acc, item) => {
const itemIndex = acc.findIndex((i) => i.label === item);
if (itemIndex === -1) {
acc.push({ label: item, percentage: 1 / props.items.length });
} else {
acc[itemIndex].percentage += 1 / props.items.length;
}
return acc;
}, [] as ChartItem[]);
return chartItems.sort(sort);
});
function sort(a: ChartItem, b: ChartItem) {
if (a.label > b.label) {
return -1;
}
if (a.label < b.label) {
return 1;
}
return 0;
}
</script> </script>

View File

@ -126,7 +126,7 @@
"receivedMaterialsLabel": "Haben Sie Vorbereitungsunterlagen (z.B. eLearning) erhalten?", "receivedMaterialsLabel": "Haben Sie Vorbereitungsunterlagen (z.B. eLearning) erhalten?",
"materialsRatingLabel": "Falls ja: Wie beurteilen Sie die Vorbereitungsunterlagen (z.B. eLearning)?", "materialsRatingLabel": "Falls ja: Wie beurteilen Sie die Vorbereitungsunterlagen (z.B. eLearning)?",
"instructorCompetenceLabel": "Der Kursleiter war themenstark, fachkompetent.", "instructorCompetenceLabel": "Der Kursleiter war themenstark, fachkompetent.",
"instructorRespectLabel": "Fragen und Anregungen der Kursteilnehmenden wurden ernst genommen u. aufgegriffen.", "instructorRespectLabel": "Fragen und Anregungen der Kursteilnehmenden wurden ernst genommen und aufgegriffen.",
"instructorOpenFeedbackLabel": "Was ich dem Kursleiter sonst noch sagen wollte:", "instructorOpenFeedbackLabel": "Was ich dem Kursleiter sonst noch sagen wollte:",
"courseNegativeFeedbackLabel": "Wo sehen Sie Verbesserungspotenzial?", "courseNegativeFeedbackLabel": "Wo sehen Sie Verbesserungspotenzial?",
"coursePositiveFeedbackLabel": "Was hat Ihnen besonders gut gefallen?", "coursePositiveFeedbackLabel": "Was hat Ihnen besonders gut gefallen?",

View File

@ -1,9 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import IconLogout from "@/components/icons/IconLogout.vue"; import IconLogout from "@/components/icons/IconLogout.vue";
import IconSettings from "@/components/icons/IconSettings.vue"; import IconSettings from "@/components/icons/IconSettings.vue";
import HorizontalBarChart, {
type ChartItem,
} from "@/components/ui/HorizontalBarChart.vue";
import ItCheckbox from "@/components/ui/ItCheckbox.vue"; import ItCheckbox from "@/components/ui/ItCheckbox.vue";
import ItCheckboxGroup from "@/components/ui/ItCheckboxGroup.vue"; import ItCheckboxGroup from "@/components/ui/ItCheckboxGroup.vue";
import ItDropdown from "@/components/ui/ItDropdown.vue"; import ItDropdown from "@/components/ui/ItDropdown.vue";
@ -12,6 +9,7 @@ import ItRadioGroup from "@/components/ui/ItRadioGroup.vue";
import ItTextarea from "@/components/ui/ItTextarea.vue"; import ItTextarea from "@/components/ui/ItTextarea.vue";
import RatingScale from "@/components/ui/RatingScale.vue"; import RatingScale from "@/components/ui/RatingScale.vue";
import VerticalBarChart from "@/components/ui/VerticalBarChart.vue"; import VerticalBarChart from "@/components/ui/VerticalBarChart.vue";
import HorizontalBarChart from "@/components/ui/HorizontalBarChart.vue";
import logger from "loglevel"; import logger from "loglevel";
import { reactive, ref } from "vue"; import { reactive, ref } from "vue";
@ -102,23 +100,17 @@ const sourceItems = [
const textValue = ref("abc"); const textValue = ref("abc");
const barChartItems: ChartItem[] = [ const barChartItems = [
{ "Internet",
percentage: 1, "Internet",
label: "100%", "Internet",
}, "Internet",
{ "Internet",
percentage: 0.9, "Internet",
label: "Internet", "TV",
}, "TV",
{ "TV",
percentage: 0.1, "Anderes"
label: "Anderes",
},
{
percentage: 0,
label: "Anderes",
},
]; ];
function log(data: any) { function log(data: any) {
@ -455,7 +447,7 @@ function log(data: any) {
title="Frage 3" title="Frage 3"
text="Wie zufrieden bist du mit dem Kurs “Überbetriebliche Kurse” im Allgemeinen?" text="Wie zufrieden bist du mit dem Kurs “Überbetriebliche Kurse” im Allgemeinen?"
/> />
<VerticalBarChart title="Frage X" text="Fragentext" :ratio="0.2" /> <VerticalBarChart title="Frage X" text="Fragentext" :ratings="[true, true, false, true, true, false, true, false]" />
<HorizontalBarChart title="Frage X" text="Fragentext" :items="barChartItems" /> <HorizontalBarChart title="Frage X" text="Fragentext" :items="barChartItems" />
</div> </div>
</main> </main>

View File

@ -35,16 +35,17 @@
/> />
<OpenFeedback <OpenFeedback
class="mb-8 bg-white" class="mb-8 bg-white"
v-if="openKeys.includes(question.key)" v-else-if="openKeys.includes(question.key)"
:title="`${$t('feedback.questionTitle')} ${i + 1}`" :title="`${$t('feedback.questionTitle')} ${i + 1}`"
:text="question.question" :text="question.question"
:answers="feedbackData.questions[question.key].filter((a) => a !== '')" :answers="feedbackData.questions[question.key].filter((a: string) => a !== '')"
></OpenFeedback> ></OpenFeedback>
<!-- HorizontalBarChart <HorizontalBarChart
v-else class="mb-8 bg-white"
v-else-if="horizontalChartKeys.includes(question.key)"
:title="`${$t('feedback.questionTitle')} ${i}`" :title="`${$t('feedback.questionTitle')} ${i}`"
:text="question.question" :text="question.question"
:items="barChartItems" /--> :items="feedbackData.questions[question.key].map((a: string) => `${a}%`)" />
</li> </li>
</ol> </ol>
</main> </main>
@ -87,7 +88,7 @@ const orderedQuestions = [
question: t("feedback.goalAttainmentLabel"), question: t("feedback.goalAttainmentLabel"),
}, },
{ {
key: "proficieny", key: "proficiency",
question: t("feedback.proficiencyLabel"), question: t("feedback.proficiencyLabel"),
}, },
{ {
@ -128,6 +129,7 @@ const ratingKeys = [
"instructor_respect", "instructor_respect",
]; ];
const verticalChartKyes = ["received_materials", "would_recommend"]; const verticalChartKyes = ["received_materials", "would_recommend"];
const horizontalChartKeys = ["proficiency"];
const openKeys = [ const openKeys = [
"course_negative_feedback", "course_negative_feedback",
"course_positive_feedback", "course_positive_feedback",
@ -144,13 +146,6 @@ onMounted(async () => {
Object.assign(feedbackData, data); Object.assign(feedbackData, data);
}); });
function calculateAverage(ratings: number[]): number {
return ratings.reduce((a, b) => a + b, 0) / ratings.length;
}
function getNumberOfAnswers(ratings: number[]): number {
return ratings.filter((r) => typeof r !== "string" || r !== "").length;
}
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -26,7 +26,7 @@ class FeedbackFactory(DjangoModelFactory):
course_positive_feedback = FuzzyChoice( course_positive_feedback = FuzzyChoice(
[ [
"Die Präsentation war super", "Die Präsentation war super",
"Das Beispiel mit der Katze fand ich sehr veranschaulicht!", "Das Beispiel mit der Katze fand ich sehr gut veranschaulicht!",
] ]
) )
course_negative_feedback = FuzzyChoice( course_negative_feedback = FuzzyChoice(