147 lines
4.6 KiB
Vue
147 lines
4.6 KiB
Vue
<template>
|
|
<QuestionSummary :title="props.title" :text="props.text">
|
|
<div class="mb-6 inline-grid grid-cols-[140px_auto] grid-rows-2 gap-x-2">
|
|
<h4 class="text-xl font-bold">{{ $t("feedback.average") }}:</h4>
|
|
<span
|
|
class="col-start-2 row-span-2 inline-flex h-9 w-11 items-center justify-center rounded text-xl font-bold"
|
|
:style="ratingValueStyle"
|
|
data-cy="rating-scale-average"
|
|
>
|
|
{{ rating.toFixed(1) }}
|
|
</span>
|
|
<h5 class="text-base">{{ answers }} {{ $t("feedback.answers") }}</h5>
|
|
</div>
|
|
<div
|
|
class="relative grid grid-cols-3 pt-3 grid-areas-rating-scale-slim md:grid-cols-8 md:grid-areas-rating-scale"
|
|
>
|
|
<div
|
|
class="gradient relative z-10 h-2 overflow-visible grid-in-bar"
|
|
:style="gradientStyle"
|
|
></div>
|
|
<div class="empty-bar absolute h-2 w-full bg-gray-300 grid-in-bar"></div>
|
|
<div class="circle-wrapper relative grid-in-bar">
|
|
<div
|
|
class="circle absolute -top-3 z-20 h-8 w-8 -translate-x-1/2 rounded-full p-[3px] before:block before:h-full before:w-full before:rounded-full before:bg-white"
|
|
:style="circleStyle"
|
|
></div>
|
|
</div>
|
|
<div
|
|
v-for="(legend, i) of legends"
|
|
:key="legend.index"
|
|
:class="{
|
|
'items-start grid-in-fst before:left-0 md:before:left-1/2': legend.index == 1,
|
|
'grid-in-mid before:left-0 md:grid-in-snd md:before:left-1/2':
|
|
legend.index == 2,
|
|
'hidden before:left-1/2 md:inline-flex md:grid-in-trd': legend.index == 3,
|
|
'items-end text-right grid-in-fth before:right-0 after:absolute after:left-0 after:top-0 after:h-4 after:w-px after:bg-gray-500 md:before:left-1/2 md:before:right-auto md:after:hidden':
|
|
legend.index == 4,
|
|
}"
|
|
class="legend relative inline-flex flex-col pt-4 before:absolute before:top-0 before:h-4 before:w-px before:bg-gray-500 md:items-center"
|
|
>
|
|
<Popover class="relative">
|
|
<PopoverButton class="focus:outline-none">
|
|
<div
|
|
:class="[
|
|
{ hidden: legend.index === 3 || legend.index === 2 },
|
|
'md:block',
|
|
]"
|
|
>
|
|
{{ legend.index }}
|
|
</div>
|
|
|
|
<p
|
|
:class="[
|
|
{ hidden: legend.index === 3 || legend.index === 2 },
|
|
'md:block',
|
|
]"
|
|
>
|
|
{{ legend.label }}
|
|
</p>
|
|
</PopoverButton>
|
|
<PopoverPanel
|
|
class="absolute top-[-150%] z-10 w-[120px] border border-gray-500 bg-white p-1 text-left text-sm"
|
|
>
|
|
<p>
|
|
{{ `"${legend.label}" ${numberOfRatings[i]} ${$t("feedback.answers")}` }}
|
|
</p>
|
|
</PopoverPanel>
|
|
</Popover>
|
|
</div>
|
|
</div>
|
|
</QuestionSummary>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import QuestionSummary from "@/components/ui/QuestionSummary.vue";
|
|
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
|
|
import { computed } from "vue";
|
|
import { useTranslation } from "i18next-vue";
|
|
import log from "loglevel";
|
|
import { getBlendedColorForRating } from "@/utils/ratingToColor";
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const legends = [
|
|
{ index: 1, label: t("feedback.veryUnhappy") },
|
|
{ index: 2, label: t("feedback.unhappy") },
|
|
{ index: 3, label: t("feedback.happy") },
|
|
{ index: 4, label: t("feedback.veryHappy") },
|
|
];
|
|
|
|
const props = defineProps<{
|
|
ratings: number[];
|
|
title: string;
|
|
text: string;
|
|
}>();
|
|
|
|
log.debug("RatingScale created", props);
|
|
|
|
const rating = computed(() => {
|
|
const sum = props.ratings.reduce((a, b) => a + b, 0);
|
|
return sum / props.ratings.length;
|
|
});
|
|
|
|
const answers = computed(() => props.ratings.length);
|
|
|
|
const numberOfRatings = computed(() => {
|
|
return legends.map(
|
|
(legend) => props.ratings.filter((rating) => rating === legend.index).length
|
|
);
|
|
});
|
|
|
|
const percent = computed(() => {
|
|
return (Math.floor(rating.value) - 1) * 33.33 + (rating.value % 1) * 33.33;
|
|
});
|
|
|
|
const leftPosition = computed(() => `${percent.value.toPrecision(3)}%`);
|
|
const rightClip = computed(() => `${Math.round(100 - percent.value)}%`);
|
|
|
|
const backgroundColor = getBlendedColorForRating(rating.value);
|
|
|
|
const circleStyle = {
|
|
backgroundColor,
|
|
left: leftPosition.value,
|
|
};
|
|
|
|
const gradientStyle = {
|
|
clipPath: `inset(0 ${rightClip.value} 0 0)`,
|
|
};
|
|
|
|
const ratingValueStyle = {
|
|
backgroundColor,
|
|
};
|
|
</script>
|
|
|
|
<style lang="postcss" scoped>
|
|
.gradient {
|
|
background-color: theme(colors.yellow.400);
|
|
background: linear-gradient(
|
|
90deg,
|
|
theme(colors.red.600) 0%,
|
|
theme(colors.yellow.500) 33%,
|
|
theme(colors.green.500) 66%,
|
|
theme(colors.green.600) 100%
|
|
);
|
|
}
|
|
</style>
|