vbv/client/src/components/ui/RatingScale.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>