+analytics update
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
<div class="flex flex-row rounded-full px-2 justify-between overflow-x">
|
<div class="flex flex-row rounded-full px-2 justify-between overflow-x">
|
||||||
<div class="flex flex-col gap-2 p-2">
|
<div class="flex flex-col gap-2 p-2">
|
||||||
<router-link to="/analytics" class="items-center flex flex-col gap-2">
|
<router-link to="/analytics" class="items-center flex flex-col gap-2">
|
||||||
<i class="pi pi-briefcase text-2xl" style="font-size: 1.5rem"></i>
|
<i class="pi pi-chart-line text-2xl" style="font-size: 1.5rem"></i>
|
||||||
<p>Аналитика</p>
|
<p>Аналитика</p>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import Column from "primevue/column";
|
|||||||
import Chart from "primevue/chart";
|
import Chart from "primevue/chart";
|
||||||
import Listbox from "primevue/listbox";
|
import Listbox from "primevue/listbox";
|
||||||
import Select from "primevue/select";
|
import Select from "primevue/select";
|
||||||
|
import InputText from "primevue/inputtext";
|
||||||
|
import IconField from "primevue/iconfield"
|
||||||
|
import InputIcon from "primevue/inputicon";
|
||||||
import Accordion from "primevue/accordion";
|
import Accordion from "primevue/accordion";
|
||||||
import AccordionPanel from "primevue/accordionpanel";
|
import AccordionPanel from "primevue/accordionpanel";
|
||||||
import AccordionHeader from "primevue/accordionheader";
|
import AccordionHeader from "primevue/accordionheader";
|
||||||
@@ -27,8 +30,15 @@ const dataTableCategories = ref([]);
|
|||||||
const tableColumns = ref([]);
|
const tableColumns = ref([]);
|
||||||
const chartData = ref(null)
|
const chartData = ref(null)
|
||||||
const selectedCategory = ref()
|
const selectedCategory = ref()
|
||||||
const formatter = ref(new Intl.NumberFormat('ru-RU', {style: 'currency', currency: 'RUB', minimumFractionDigits: 0, maximumFractionDigits:0}))
|
const formatter = ref(new Intl.NumberFormat('ru-RU', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'RUB',
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0
|
||||||
|
}))
|
||||||
const isChartOpen = ref(false)
|
const isChartOpen = ref(false)
|
||||||
|
const filterText = ref(null)
|
||||||
|
|
||||||
|
|
||||||
const closeChart = () => {
|
const closeChart = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -148,12 +158,12 @@ const prepareTableData = (categories) => {
|
|||||||
|
|
||||||
if (found.difference != 0) {
|
if (found.difference != 0) {
|
||||||
if (found.difference > 0) {
|
if (found.difference > 0) {
|
||||||
row[dateStr] = found ? formatter.value.format(found.total) + "<p class='text-red-500 text-sm'> (+ " + found.difference + "%)</p>" : 0;
|
row[dateStr] = found ? "<p>" + formatter.value.format(found.total) + "</p><p class='text-red-500 text-sm'> (+ " + found.difference + "%)</p>" : 0;
|
||||||
} else {
|
} else {
|
||||||
row[dateStr] = found ? formatter.value.format(found.total) + "<p class='text-green-600 text-sm'> (" + found.difference + "%)</p>" : 0;
|
row[dateStr] = found ? "<p>" + formatter.value.format(found.total) + "</p><p class='text-green-600 text-sm'> (" + found.difference + "%)</p>" : 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
row[dateStr] = found ? formatter.value.format(found.total) : 0;
|
row[dateStr] = found ? "<p>" + formatter.value.format(found.total) + "</p>" : 0;
|
||||||
}
|
}
|
||||||
if (!sums[dateStr]) {
|
if (!sums[dateStr]) {
|
||||||
sums[dateStr] = 0
|
sums[dateStr] = 0
|
||||||
@@ -161,7 +171,7 @@ const prepareTableData = (categories) => {
|
|||||||
sums[dateStr] += found.total
|
sums[dateStr] += found.total
|
||||||
categorySum += found.total
|
categorySum += found.total
|
||||||
});
|
});
|
||||||
row["avg"] = formatter.value.format(categorySum/allDates.length);
|
row["avg"] = "<p>" + formatter.value.format(categorySum / allDates.length) + "</p>";
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
@@ -179,7 +189,7 @@ const prepareTableData = (categories) => {
|
|||||||
if (difference > 0) {
|
if (difference > 0) {
|
||||||
color = "text-red-500"
|
color = "text-red-500"
|
||||||
} else color = "text-green-600"
|
} else color = "text-green-600"
|
||||||
sums[key] = formatter.value.format(sums[key]) + `<p class='${color}'>(` + difference.toFixed(0) + "%)</p>";
|
sums[key] = "<p>" + formatter.value.format(sums[key]) + `</p><p class='${color}'>(` + difference.toFixed(0) + "%)</p>";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -221,32 +231,54 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LoadingView v-if="loading"/>
|
<LoadingView v-if="loading"/>
|
||||||
<div v-else class="p-4 bg-gray-100 h-full flex flex-col gap-4 items-center justify-items-center ">
|
<div v-else class="p-4 bg-gray-100 flex flex-col gap-4 items-center justify-items-center ">
|
||||||
|
<div class="!items-center w-5/6 bg-white">
|
||||||
|
<Accordion value="1" class=" " @tab-open="isChartOpen=true"
|
||||||
|
@tab-close="closeChart">
|
||||||
|
<AccordionPanel value="0">
|
||||||
|
<AccordionHeader>График</AccordionHeader>
|
||||||
|
<AccordionContent>
|
||||||
|
|
||||||
<Accordion value="0" class=" !w-5/6 !items-center !justify-items-start" @tab-open="isChartOpen=true"
|
<!-- <Select v-model="selectedCategory" :options="categoriesCatalog" optionLabel="name"-->
|
||||||
@tab-close="closeChart">
|
<!-- placeholder="Выберите категории"-->
|
||||||
<AccordionPanel value="1">
|
<!-- :maxSelectedLabels="3" class="w-full md:w-80"/>-->
|
||||||
<AccordionHeader>График</AccordionHeader>
|
<div v-if="isChartOpen"
|
||||||
<AccordionContent class="items-center justify-items-center ">
|
class="grid grid-cols-1 sm:grid-cols-6 w-full items-start justify-items-start">
|
||||||
|
<!-- Список категорий -->
|
||||||
|
<div class="sm:col-span-1 w-full h-56 sm:h-[42rem] p-2 overflow-y-scroll outline outline-1 outline-gray-300 rounded-lg">
|
||||||
|
<!-- <IconField>-->
|
||||||
|
|
||||||
<!-- <Select v-model="selectedCategory" :options="categoriesCatalog" optionLabel="name"-->
|
<!-- <InputText v-model="filterText" placeholder="Поиск категории"/>-->
|
||||||
<!-- placeholder="Выберите категории"-->
|
<!-- <InputIcon class="pi pi-search"/>-->
|
||||||
<!-- :maxSelectedLabels="3" class="w-full md:w-80"/>-->
|
<!-- </IconField>-->
|
||||||
<div v-if="isChartOpen" class="flex flex-row items-start justify-items-start w-full">
|
<ul class="w-5/6">
|
||||||
<Listbox v-model="selectedCategory" :options="categoriesCatalog" filter optionLabel="name"
|
|
||||||
class="!w-fit !h-5/6 md:w-56">
|
|
||||||
|
|
||||||
<template #option="slotProps">
|
<li v-for="category in categoriesCatalog"
|
||||||
<div>{{ slotProps.option.icon }} {{ slotProps.option.name }}</div>
|
class="tpx-4 py-2 hover:bg-blue-50 rounded-md "
|
||||||
</template>
|
:class="selectedCategory.id == category.id? '!bg-emerald-50 text-emerald-700': '' " @click="selectedCategory=category">
|
||||||
|
{{ category.icon }} {{ category.name }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
</Listbox>
|
<!-- Контейнер для графика с горизонтальной прокруткой -->
|
||||||
<Chart type="line" :data="preparedChartData" :options="chartOptions" class="!w-5/6 !h-full"/>
|
<div class="sm:col-span-5 overflow-x-auto w-full">
|
||||||
</div>
|
<!-- «Растяжка», чтобы было за что «скроллить» -->
|
||||||
</AccordionContent>
|
<div class="min-w-[550px] ">
|
||||||
</AccordionPanel>
|
<Chart
|
||||||
</Accordion>
|
type="line"
|
||||||
|
:data="preparedChartData"
|
||||||
|
:options="chartOptions"
|
||||||
|
class="h-64 sm:h-full sm:w-full "
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionPanel>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
|
||||||
<DataTable :value="dataTableCategories" responsiveLayout="scroll" filter stripedRows class="w-5/6 items-center">
|
<DataTable :value="dataTableCategories" responsiveLayout="scroll" filter stripedRows class="w-5/6 items-center">
|
||||||
<Column
|
<Column
|
||||||
@@ -281,7 +313,18 @@ onMounted(async () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-listbox-list-container {
|
.p-listbox {
|
||||||
max-height: 100% !important;
|
//height: 80% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-listbox-list-container {
|
||||||
|
//height: 100% !important;
|
||||||
|
//max-height: 90% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-chart {
|
||||||
|
//width: 100% !important;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,8 +4,14 @@ import {Category} from "@/models/Category"; // Импортируете нас
|
|||||||
|
|
||||||
export const getCategories = async (type = null) => {
|
export const getCategories = async (type = null) => {
|
||||||
|
|
||||||
type = type ? type : ''
|
|
||||||
return await apiClient.get('/categories?type=' + type);
|
const params = {};
|
||||||
|
if (type) {
|
||||||
|
params.type = type;
|
||||||
|
}
|
||||||
|
return await apiClient.get('/categories', {
|
||||||
|
params: params
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCategoryTypes = async () => {
|
export const getCategoryTypes = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user