+analytics update

This commit is contained in:
Vladimir Voronin
2025-01-24 15:25:43 +03:00
parent a685c67395
commit 26dd9c5202
3 changed files with 81 additions and 32 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 () => {