feat: add rating colors
This commit is contained in:
parent
dfe1bfb845
commit
3664ddf6d7
|
|
@ -28,10 +28,6 @@ defineProps<{
|
||||||
<span>{{ $t("a.Durchführungen") }}</span>
|
<span>{{ $t("a.Durchführungen") }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- TODO Figure out where this link should go -->
|
|
||||||
<!-- <router-link class="btn-secondary" to="#">-->
|
|
||||||
<!-- {{ $t("a.Liste anzeigen") }}-->
|
|
||||||
<!-- </router-link>-->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,29 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import BaseBox from "@/components/dashboard/BaseBox.vue";
|
import BaseBox from "@/components/dashboard/BaseBox.vue";
|
||||||
|
import { getBlendedColorForRating } from "@/utils/ratingToColor";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
feedbackCount: number;
|
feedbackCount: number;
|
||||||
statisfactionMax: number;
|
statisfactionMax: number;
|
||||||
statisfactionAvg: number;
|
statisfactionAvg: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const satisfactionColor = computed(() => {
|
||||||
|
return getBlendedColorForRating(props.statisfactionAvg);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BaseBox :details-link="'/statistic/feedback'">
|
<BaseBox :details-link="'/statistic/feedback'">
|
||||||
<template #title>{{ $t("a.Feedback Teilnehmer") }}</template>
|
<template #title>{{ $t("a.Feedback Teilnehmer") }}</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="flex">
|
<div class="flex items-center">
|
||||||
<!-- Left Pane -->
|
<!-- Left Pane -->
|
||||||
<div class="flex h-16 w-32 items-center justify-center rounded bg-green-500">
|
<div
|
||||||
|
class="mr-3 flex items-center justify-center rounded p-4"
|
||||||
|
:style="{ backgroundColor: satisfactionColor }"
|
||||||
|
>
|
||||||
<i18next :translation="$t('a.{AVG} von {MAX}')">
|
<i18next :translation="$t('a.{AVG} von {MAX}')">
|
||||||
<template #AVG>
|
<template #AVG>
|
||||||
<span class="pr-2 text-4xl font-bold">
|
<span class="pr-2 text-4xl font-bold">
|
||||||
|
|
@ -27,7 +36,7 @@ const props = defineProps<{
|
||||||
</i18next>
|
</i18next>
|
||||||
</div>
|
</div>
|
||||||
<!-- Right Pane -->
|
<!-- Right Pane -->
|
||||||
<div class="flex flex-col items-center space-y-1 p-2">
|
<div class="flex flex-col items-center space-y-1">
|
||||||
<span class="font-bold">{{ $t("a.Allgemeine Zufriedenheit") }}</span>
|
<span class="font-bold">{{ $t("a.Allgemeine Zufriedenheit") }}</span>
|
||||||
<div class="self-start">
|
<div class="self-start">
|
||||||
<i18next :translation="$t('a.Total {NUMBER} Antworten')">
|
<i18next :translation="$t('a.Total {NUMBER} Antworten')">
|
||||||
|
|
|
||||||
|
|
@ -77,15 +77,10 @@ import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
|
import { getBlendedColorForRating } from "@/utils/ratingToColor";
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
type RGB = [number, number, number];
|
|
||||||
const red: RGB = [221, 103, 81]; // red-600
|
|
||||||
const yellow: RGB = [250, 200, 82]; // yellow-500
|
|
||||||
const lightGreen: RGB = [120, 222, 163]; // green-500
|
|
||||||
const darkGreen: RGB = [91, 183, 130]; // green-600
|
|
||||||
|
|
||||||
const legends = [
|
const legends = [
|
||||||
{ index: 1, label: t("feedback.veryUnhappy") },
|
{ index: 1, label: t("feedback.veryUnhappy") },
|
||||||
{ index: 2, label: t("feedback.unhappy") },
|
{ index: 2, label: t("feedback.unhappy") },
|
||||||
|
|
@ -101,19 +96,11 @@ const props = defineProps<{
|
||||||
|
|
||||||
log.debug("RatingScale created", props);
|
log.debug("RatingScale created", props);
|
||||||
|
|
||||||
const rating = computed((): number => {
|
const rating = computed(() => {
|
||||||
const sum = props.ratings.reduce((a, b) => a + b, 0);
|
const sum = props.ratings.reduce((a, b) => a + b, 0);
|
||||||
return sum / props.ratings.length;
|
return sum / props.ratings.length;
|
||||||
});
|
});
|
||||||
|
|
||||||
const weight = computed(() => {
|
|
||||||
return rating.value % 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
const scale = computed(() => {
|
|
||||||
return Math.floor(rating.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const answers = computed(() => props.ratings.length);
|
const answers = computed(() => props.ratings.length);
|
||||||
|
|
||||||
const numberOfRatings = computed(() => {
|
const numberOfRatings = computed(() => {
|
||||||
|
|
@ -122,57 +109,14 @@ const numberOfRatings = computed(() => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const colors = computed(() => {
|
|
||||||
switch (scale.value) {
|
|
||||||
case 1:
|
|
||||||
return [red, yellow];
|
|
||||||
case 2:
|
|
||||||
return [yellow, lightGreen];
|
|
||||||
case 3:
|
|
||||||
default:
|
|
||||||
return [lightGreen, darkGreen];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const blendColorValue = (v1: number, v2: number, weight: number) => {
|
|
||||||
return v1 * (1 - weight) + v2 * weight;
|
|
||||||
};
|
|
||||||
|
|
||||||
const blendColors = (c1: RGB, c2: RGB, weight: number): RGB => {
|
|
||||||
const [r1, g1, b1] = c1;
|
|
||||||
const [r2, g2, b2] = c2;
|
|
||||||
return [
|
|
||||||
blendColorValue(r1, r2, weight),
|
|
||||||
blendColorValue(g1, g2, weight),
|
|
||||||
blendColorValue(b1, b2, weight),
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRGBString = ([r, g, b]: RGB) => {
|
|
||||||
return `rgb(${Math.floor(r)}, ${Math.floor(g)}, ${Math.floor(b)})`;
|
|
||||||
};
|
|
||||||
|
|
||||||
// const getRGBStyle = (c1: RGB, c2: RGB, weight: number) => {
|
|
||||||
// return getRGBString(blendColors(c1, c2, weight));
|
|
||||||
// };
|
|
||||||
|
|
||||||
const percent = computed(() => {
|
const percent = computed(() => {
|
||||||
return (scale.value - 1) * 33.33 + weight.value * 33.33;
|
return (Math.floor(rating.value) - 1) * 33.33 + (rating.value % 1) * 33.33;
|
||||||
});
|
});
|
||||||
|
|
||||||
const leftPosition = computed(() => {
|
const leftPosition = computed(() => `${percent.value.toPrecision(3)}%`);
|
||||||
return `${percent.value.toPrecision(3)}%`;
|
const rightClip = computed(() => `${Math.round(100 - percent.value)}%`);
|
||||||
});
|
|
||||||
|
|
||||||
const rightClip = computed(() => {
|
const backgroundColor = getBlendedColorForRating(rating.value);
|
||||||
return `${Math.round(100 - percent.value)}%`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const blendedColor = computed(() => {
|
|
||||||
return blendColors(colors.value[0], colors.value[1], weight.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const backgroundColor = getRGBString(blendedColor.value);
|
|
||||||
|
|
||||||
const circleStyle = {
|
const circleStyle = {
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import type {
|
||||||
} from "@/gql/graphql";
|
} from "@/gql/graphql";
|
||||||
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
import StatisticFilterList from "@/components/dashboard/StatisticFilterList.vue";
|
||||||
import { useCourseStatistics } from "@/composables";
|
import { useCourseStatistics } from "@/composables";
|
||||||
|
import { getBlendedColorForRating } from "@/utils/ratingToColor";
|
||||||
|
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
|
|
||||||
|
|
@ -50,15 +51,25 @@ const { courseSessionName, circleMeta } = useCourseStatistics();
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-4 flex items-center space-x-2">
|
<div class="mb-4 flex items-center space-x-2">
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-center rounded bg-green-500 px-2 py-1"
|
class="rounded px-2 py-1"
|
||||||
|
:style="{ backgroundColor: getBlendedColorForRating((item as FeedbackStatisticsRecordType).satisfaction_average) }"
|
||||||
>
|
>
|
||||||
<span class="font-bold">
|
<i18next :translation="$t('a.{AVG} von {MAX}')">
|
||||||
{{ (item as FeedbackStatisticsRecordType).satisfaction_average }}
|
<template #AVG>
|
||||||
</span>
|
<span class="font-bold">
|
||||||
von
|
{{
|
||||||
<span>
|
(
|
||||||
{{ (item as FeedbackStatisticsRecordType).satisfaction_max }}
|
item as FeedbackStatisticsRecordType
|
||||||
</span>
|
).satisfaction_average.toFixed(1)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #MAX>
|
||||||
|
<span>
|
||||||
|
{{ (item as FeedbackStatisticsRecordType).satisfaction_max }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</i18next>
|
||||||
</div>
|
</div>
|
||||||
<span>
|
<span>
|
||||||
{{ $t("a.Allgemeine Zufriedenheit") }}
|
{{ $t("a.Allgemeine Zufriedenheit") }}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
type RGB = [number, number, number];
|
||||||
|
|
||||||
|
const red: RGB = [221, 103, 81]; // red-600
|
||||||
|
const yellow: RGB = [250, 200, 82]; // yellow-500
|
||||||
|
const lightGreen: RGB = [120, 222, 163]; // green-500
|
||||||
|
const darkGreen: RGB = [91, 183, 130]; // green-600
|
||||||
|
|
||||||
|
const blendColorValue = (v1: number, v2: number, weight: number): number => {
|
||||||
|
return v1 * (1 - weight) + v2 * weight;
|
||||||
|
};
|
||||||
|
|
||||||
|
const blendColors = (c1: RGB, c2: RGB, weight: number): RGB => {
|
||||||
|
const [r1, g1, b1] = c1;
|
||||||
|
const [r2, g2, b2] = c2;
|
||||||
|
return [
|
||||||
|
blendColorValue(r1, r2, weight),
|
||||||
|
blendColorValue(g1, g2, weight),
|
||||||
|
blendColorValue(b1, b2, weight),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRGBString = (color: RGB): string => {
|
||||||
|
const [r, g, b] = color;
|
||||||
|
return `rgb(${Math.floor(r)}, ${Math.floor(g)}, ${Math.floor(b)})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBlendedColorForRating = (rating: number): string => {
|
||||||
|
const scale = Math.floor(rating);
|
||||||
|
const weight = rating % 1;
|
||||||
|
let colors: [RGB, RGB];
|
||||||
|
|
||||||
|
switch (scale) {
|
||||||
|
case 1:
|
||||||
|
colors = [red, yellow];
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
colors = [yellow, lightGreen];
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
default:
|
||||||
|
colors = [lightGreen, darkGreen];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blendedColor = blendColors(colors[0], colors[1], weight);
|
||||||
|
return getRGBString(blendedColor);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue