+analytics update

This commit is contained in:
Vladimir Voronin
2025-01-23 14:49:41 +03:00
parent a9f9056ca0
commit 82c2b4be6a
2 changed files with 129 additions and 36 deletions

View File

@@ -1,36 +1,57 @@
<script setup lang="ts"> <script setup lang="ts">
import LoadingView from "@/components/LoadingView.vue"; import LoadingView from "@/components/LoadingView.vue";
import {computed, onMounted, ref} from "vue"; import {computed, onMounted, ref} from "vue";
import {getCategoriesSumsRequest} from "@/services/categoryService"; import {getCategories, getCategoriesSumsRequest} from "@/services/categoryService";
import DataTable from "primevue/datatable"; import DataTable from "primevue/datatable";
import Column from "primevue/column"; import Column from "primevue/column";
import Chart from "primevue/chart"; import Chart from "primevue/chart";
import Listbox from "primevue/listbox";
import Select from "primevue/select";
import Accordion from "primevue/accordion";
import AccordionPanel from "primevue/accordionpanel";
import AccordionHeader from "primevue/accordionheader";
import AccordionContent from "primevue/accordioncontent"
import ChartDataLabels from 'chartjs-plugin-datalabels'; import ChartDataLabels from 'chartjs-plugin-datalabels';
import {Chart as ChartJS} from 'chart.js/auto'; import {Chart as ChartJS} from 'chart.js/auto';
import {Colors} from 'chart.js';
import {getMonthName} from "@/utils/utils";
const loading = ref(false); ChartJS.register(ChartDataLabels);
ChartJS.register(Colors);
const loading = ref(true);
const categoriesCatalog = ref([])
const categories = ref([]); const categories = ref([]);
const dataTableCategories = ref([]); const dataTableCategories = ref([]);
const tableColumns = ref([]); const tableColumns = ref([]);
const chartData = ref(null)
const selectedCategory = ref()
const chartData = computed(() => {
console.log('categories.value[0]:', categories.value[0]); // Логируем объект const preparedChartData = computed(() => {
if (!categories.value || !categories.value[0] || !categories.value[0].monthlySums) { if (categories.value && selectedCategory.value) {
console.warn('monthlySums отсутствует или данные не загружены'); const r = Math.random() * 255
return []; // или null, или другое значение по умолчанию const g = Math.random() * 255
const b = Math.random() * 255
const colorLine = `rgba(${r}, ${g}, ${b},1)` // Случайный цвет для графика
const colorBackground = `rgba(${r}, ${g}, ${b},0.2)` // Случайный цвет для графика
return {
labels: categories.value[0].monthlySums.map((month) => month.date), // Используем даты как метки
datasets: [{
label: categories.value.filter((it) => selectedCategory.value.id == it._id)[0].categoryName, // Название категории
data: categories.value.filter((it) => selectedCategory.value.id == it._id)[0].monthlySums.map((month) => month.total), // Данные по total
borderColor: colorLine,
fill: true,
backgroundColor: colorBackground,
tension: 0.4
}]
} }
return {labels: categories.value[0].monthlySums.map((month) => month.date), // Используем даты как метки
datasets: categories.value.map((category) => ({
label: category.categoryName, // Название категории
data: category.monthlySums.map((month) => month.total), // Данные по total
borderColor: `#${Math.floor(Math.random() * 16777215).toString(16)}`, // Случайный цвет для графика
fill: false,
}))
} }
}); })
const chartOptions = computed({ const chartOptions = {
responsive: true, responsive: true,
plugins: { plugins: {
legend: { legend: {
@@ -41,6 +62,27 @@ const chartOptions = computed({
display: true, display: true,
text: "График расходов по категориям", text: "График расходов по категориям",
}, },
colors: {
enabled: true,
},
datalabels: {
formatter: function (value) {
return value + " ₽";
},
color: 'blue',
align: 'top',
offset: 2,
labels: {
title: {
font: {
weight: 'bold'
}
},
value: {
color: 'green'
}
}
}
}, },
scales: { scales: {
x: { x: {
@@ -56,7 +98,7 @@ const chartOptions = computed({
}, },
}, },
}, },
}); }
// Преобразование данных для таблицы // Преобразование данных для таблицы
const prepareTableData = (categories) => { const prepareTableData = (categories) => {
@@ -71,7 +113,7 @@ const prepareTableData = (categories) => {
// 2. Сортируем даты. // 2. Сортируем даты.
// Если у вас формат "YYYY-MM-DD", лексикографическая сортировка работает корректно хронологически: // Если у вас формат "YYYY-MM-DD", лексикографическая сортировка работает корректно хронологически:
allDates.sort((a, b) => a.localeCompare(b)); // allDates.sort((a, b) => a.localeCompare(b));
// (Если хотите гарантированно использовать объекты Date, можно так: // (Если хотите гарантированно использовать объекты Date, можно так:
// allDates.sort((a, b) => new Date(a) - new Date(b)); // allDates.sort((a, b) => new Date(a) - new Date(b));
@@ -81,27 +123,28 @@ const prepareTableData = (categories) => {
// - Первый столбец: "category" // - Первый столбец: "category"
// - Далее по одному столбцу на каждую дату // - Далее по одному столбцу на каждую дату
tableColumns.value = [ tableColumns.value = [
{ field: "category", header: "Категория" }, {field: "category", header: "Категория"},
...allDates.map((dateStr) => ({ field: dateStr, header: dateStr })), ...allDates.map((dateStr) => ({field: dateStr, header: dateStr})),
]; ];
console.log(tableColumns.value[0].field); console.log(tableColumns.value[0].field);
// 4. Формируем строки (для каждой категории) // 4. Формируем строки (для каждой категории)
return categories.map((category) => { return categories.map((category) => {
// Начинаем со строки, где есть поле с именем категории // Начинаем со строки, где есть поле с именем категории
const row = {category: category.categoryIcon + " "+category.categoryName}; const row = {category: category.categoryIcon + " " + category.categoryName};
const formatter = new Intl.NumberFormat('ru-RU', {style: 'currency', currency: 'RUB', minimumFractionDigits: 0});
// Для каждой даты проверяем, есть ли в monthlySums соответствующая запись // Для каждой даты проверяем, есть ли в monthlySums соответствующая запись
allDates.forEach((dateStr) => { allDates.forEach((dateStr) => {
const found = category.monthlySums.find((m) => m.date === dateStr); const found = category.monthlySums.find((m) => m.date === dateStr);
if (found.difference != 0) { if (found.difference != 0) {
if (found.difference > 0) { if (found.difference > 0) {
row[dateStr] = found ? found.total + " (+ " + found.difference + "%)" : 0; row[dateStr] = found ? formatter.format(found.total) + "<p class='text-green-600 text-sm'> (+ " + found.difference + "%)</p>" : 0;
} else { } else {
row[dateStr] = found ? found.total + " (" + found.difference + "%)" : 0; row[dateStr] = found ? formatter.format(found.total) + "<p class='text-red-500 text-sm'> (" + found.difference + "%)</p>" : 0;
} }
} else { } else {
row[dateStr] = found ? found.total : 0;
row[dateStr] = found ? formatter.format(found.total) : 0;
} }
@@ -115,26 +158,59 @@ const prepareTableData = (categories) => {
const fetchCategoriesSums = async () => { const fetchCategoriesSums = async () => {
loading.value = true; loading.value = true;
await getCategoriesSumsRequest().then((data) => { await getCategoriesSumsRequest().then((data) => {
data.data.forEach((category) => {
category.monthlySums.forEach((monthlySum) => {
monthlySum.date = getMonthName(new Date(monthlySum.date).getMonth()) + " " + new Date(monthlySum.date).getFullYear()
})
})
categories.value = data.data; categories.value = data.data;
console.log(categories.value); console.log(categories.value);
dataTableCategories.value = prepareTableData(data.data); dataTableCategories.value = prepareTableData(data.data);
}); });
loading.value = false; loading.value = false;
}; };
const fetchCategoriesCatalog = async () => {
await getCategories("EXPENSE").then((data) => {
categoriesCatalog.value = data.data
selectedCategory.value = data.data[0]
})
}
onMounted(async () => { onMounted(async () => {
await fetchCategoriesSums(); await Promise.all([fetchCategoriesSums(), fetchCategoriesCatalog()])
// await fetchCategoriesSums();
}); });
</script> </script>
<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 h-full flex flex-col gap-4 items-center justify-items-center ">
<!-- Таблица с преобразованными данными -->
<!-- {{// chartData}}--> <Accordion value="0" class=" !w-5/6 !items-center !justify-items-start">
<!-- <Chart type="line" :data="chartData" :options="chartOptions" />--> <AccordionPanel value="1">
{{tableColumns[0].field}} <AccordionHeader>График</AccordionHeader>
<AccordionContent class="items-center justify-items-center ">
<!-- <Select v-model="selectedCategory" :options="categoriesCatalog" optionLabel="name"-->
<!-- placeholder="Выберите категории"-->
<!-- :maxSelectedLabels="3" class="w-full md:w-80"/>-->
<Listbox v-model="selectedCategory" :options="categoriesCatalog" filter optionLabel="name" class="!w-fit !h-5/6 md:w-56">
<template #option="slotProps">
<div>{{ slotProps.option.icon }} {{ slotProps.option.name }}</div>
</template>
</Listbox>
<Chart type="line" :data="preparedChartData" :options="chartOptions" class="!w-5/6 !h-full"/>
</AccordionContent>
</AccordionPanel>
</Accordion>
<DataTable :value="dataTableCategories" responsiveLayout="scroll" stripedRows class="w-5/6 items-center"> <DataTable :value="dataTableCategories" responsiveLayout="scroll" stripedRows class="w-5/6 items-center">
<Column <Column
:field="tableColumns[0].field" :field="tableColumns[0].field"
@@ -150,7 +226,24 @@ onMounted(async () => {
:key="col.field" :key="col.field"
:field="col.field" :field="col.field"
:header="col.header" :header="col.header"
/> >
<template #body="{ data }">
<span v-html="data[col.field] " class="text-center"></span>
</template>
</Column>
</DataTable> </DataTable>
</div> </div>
</template> </template>
<style>
.p-accordioncontent-content {
display: flex;
flex-direction: row;
align-items: start;
align-content: center;
justify-content: center;
}
.p-listbox-list-container {
max-height: 100% !important;
}
</style>

View File

@@ -443,7 +443,7 @@ const totalIncomeLeftToGet = computed(() => {
const totalLoans = computed(() => { const totalLoans = computed(() => {
let value = 0 let value = 0
categories.value.filter((cat) => cat.category.id == "675850148198643f121e465d").forEach(cat => { categories.value.filter((cat) => cat.category.id == "677bc767c7857460a491bd49").forEach(cat => {
value += cat.currentLimit value += cat.currentLimit
}) })
return value return value
@@ -462,7 +462,7 @@ const savingRatio = computed(() => {
const totalSaving = computed(() => { const totalSaving = computed(() => {
let value = 0 let value = 0
categories.value.filter((cat) => cat.category.id == "675850148198643f121e466a").forEach(cat => { categories.value.filter((cat) => cat.category.id == "677bc767c7857460a491bd4f").forEach(cat => {
value += cat.currentLimit value += cat.currentLimit
}) })
return value return value