| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- <!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
- <template>
- <q-card v-bind="$attrs" class="q-px-md">
- <q-card-section class="row justify-between">
- <div>
- <div class="text-h6">{{ props.title }}</div>
- <span>{{ props.subTitle }}</span>
- </div>
- <q-btn
- icon="mdi-tray-arrow-down"
- dense
- flat
- class="q-my-auto"
- @click="downloadImage"
- />
- </q-card-section>
- <q-separator dark inset />
- <div class="graph-container">
- <div class="column">
- <div class="col-4 row">
- <div
- class="bg-nps-green"
- style="width: 1rem; min-height: max-content"
- />
- <div class="column q-pa-md">
- <span class="text-bold text-h6 text-nps-green">
- {{ $t("charts.nps.promotion_zone") }}
- </span>
- <span>{{ $t("charts.nps.promotion_zone_range") }}</span>
- </div>
- </div>
- <div class="col-4 row">
- <div
- class="bg-nps-green-light"
- style="width: 1rem; min-height: max-content"
- />
- <div class="column q-pa-md">
- <span class="text-bold text-h6 text-nps-green-light">
- {{ $t("charts.nps.quality_zone") }}
- </span>
- <span>{{ $t("charts.nps.quality_zone_range") }}</span>
- </div>
- </div>
- <div class="col-4 row">
- <div
- class="bg-nps-yellow"
- style="width: 1rem; min-height: max-content"
- />
- <div class="column q-pa-md">
- <span class="text-bold text-h6 text-nps-yellow">
- {{ $t("charts.nps.refinement_zone") }}
- </span>
- <span>{{ $t("charts.nps.refinement_zone_range") }}</span>
- </div>
- </div>
- <div class="col-4 row">
- <div
- class="bg-nps-red"
- style="width: 1rem; min-height: max-content"
- />
- <div class="column q-pa-md">
- <span class="text-bold text-h6 text-nps-red">
- {{ $t("charts.nps.critical_zone") }}</span
- >
- <span>{{ $t("charts.nps.critical_zone_range") }}</span>
- </div>
- </div>
- </div>
- <div style="display: flex; flex-direction: column; align-items: center">
- <div style="max-height: 500px">
- <Doughnut
- ref="chart_ref"
- :options="chartOptions"
- :data="chartData"
- :plugins="[ChartDataLabels, gaugeNeedle]"
- />
- </div>
- <span>{{ titulo }}</span>
- </div>
- <div class="column q-col-gutter-md">
- <div class="column">
- <span>
- {{ $t("charts.nps.promoters") }} -
- {{ ((data.promotores / data.total) * 100).toFixed(0) }} %
- </span>
- <q-linear-progress
- :value="data.promotores / data.total"
- rounded
- size="20px"
- color="nps-green"
- style="min-width: 200px"
- />
- </div>
- <div class="column">
- <span
- >{{ $t("charts.nps.passives") }} -
- {{ ((data.neutros / data.total) * 100).toFixed(0) }} %</span
- >
- <q-linear-progress
- :value="data.neutros / data.total"
- rounded
- size="20px"
- color="nps-yellow"
- style="min-width: 200px"
- />
- </div>
- <div class="column">
- <span
- >{{ $t("charts.nps.detractors") }} -
- {{ ((data.detratores / data.total) * 100).toFixed(0) }} %</span
- >
- <q-linear-progress
- :value="data.detratores / data.total"
- rounded
- size="20px"
- color="nps-red"
- style="min-width: 200px"
- />
- </div>
- </div>
- </div>
- </q-card>
- </template>
- <script setup>
- import { ref } from "vue";
- import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
- import { Doughnut } from "vue-chartjs";
- import ChartDataLabels from "chartjs-plugin-datalabels";
- import { base64ToJPEG } from "src/helpers/convertBase64Image";
- const props = defineProps({
- title: {
- type: String,
- required: true,
- },
- value: {
- type: Number,
- required: true,
- },
- subTitle: {
- type: String,
- default: null,
- },
- data: {
- type: Object,
- required: true,
- },
- });
- const chart_ref = ref(null);
- const titulo = ref(`Valor: ${props.data?.nps}`);
- const gaugeNeedle = {
- id: "gaugeNeedle",
- afterDatasetsDraw(chart) {
- const { ctx, data } = chart;
- ctx.save();
- const needleValue = data.datasets[0].needleValue;
- const xCenter = chart.getDatasetMeta(0).data[0].x;
- const yCenter = chart.getDatasetMeta(0).data[0].y;
- const outerRadius = chart.getDatasetMeta(0).data[0].outerRadius - 40;
- const angle = Math.PI;
- let circumference =
- (chart.getDatasetMeta(0).data[0].circumference /
- Math.PI /
- data.datasets[0].data[0]) *
- needleValue;
- const needleAngleValue = circumference + 1.5;
- ctx.translate(xCenter, yCenter);
- ctx.rotate(angle * needleAngleValue);
- // Draw the needle
- ctx.beginPath();
- ctx.strokeStyle = "grey";
- ctx.fillStyle = "grey";
- ctx.moveTo(0 - 4, 0);
- ctx.lineTo(0, -outerRadius);
- ctx.lineTo(0 + 4, 0);
- ctx.stroke();
- ctx.fill();
- ctx.beginPath();
- ctx.arc(0, 0, 8, 0, 2 * Math.PI);
- ctx.fillStyle = "grey";
- ctx.fill();
- ctx.restore();
- },
- };
- ChartJS.register(ArcElement, Tooltip, Legend);
- const chartData = ref({
- datasets: [
- {
- backgroundColor: ["#d43333", "#ffbe00", "#40c56c", "#0d733e"],
- data: [100, 50, 25, 25],
- needleValue: Number(props.data?.nps) + 100,
- borderColor: "transparent",
- },
- ],
- });
- const chartOptions = ref({
- rotation: 270,
- circumference: 180,
- cutout: "50%",
- plugins: {
- tooltip: {
- enabled: false,
- },
- datalabels: {
- color: "white",
- font: {
- size: 14,
- weight: "bold",
- },
- formatter: (value, ctx) => {
- let valor = "";
- switch (ctx.dataIndex) {
- case 0:
- valor = "-100 a 0";
- break;
- case 1:
- valor = "0 a 50";
- break;
- case 2:
- valor = "50 a 75";
- break;
- case 3:
- valor = "75 a 100";
- break;
- default:
- valor = "";
- break;
- }
- return valor;
- },
- },
- },
- });
- addEventListener("resize", () => {
- if (chart_ref.value) {
- chart_ref.value.chart.update();
- }
- });
- const downloadImage = () => {
- const image = chart_ref.value.chart?.toBase64Image("image/jpeg", 1);
- base64ToJPEG(image, props.title);
- };
- </script>
- <style scoped>
- .graph-container {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- align-items: center;
- justify-content: space-around;
- gap: 1rem;
- width: 100%;
- max-width: 100vw;
- padding: 1rem;
- }
- @media screen and (max-width: 1024px) {
- .graph-container {
- grid-template-columns: 1fr 1fr;
- }
- }
- @media screen and (max-width: 768px) {
- .graph-container {
- grid-template-columns: 1fr;
- }
- }
- </style>
|