Add HorizontalBar component
This commit is contained in:
parent
b7038c1a9c
commit
2ab8f580bc
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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?",
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue