+analytics

This commit is contained in:
Vladimir Voronin
2025-01-23 00:16:36 +03:00
parent f60ed7c7fa
commit e4042928df
3 changed files with 111 additions and 17 deletions

View File

@@ -1,62 +1,156 @@
<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 {getCategories, getCategoriesSumsRequest} from "@/services/categoryService"; import {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 ChartDataLabels from 'chartjs-plugin-datalabels';
import {Chart as ChartJS} from 'chart.js/auto';
const loading = ref(false); const loading = ref(false);
const categories = ref([]); const categories = ref([]);
const dataTableCategories = ref([]); const dataTableCategories = ref([]);
const tableColumns = ref([]); const tableColumns = ref([]);
const chartData = computed(() => {
console.log('categories.value[0]:', categories.value[0]); // Логируем объект
if (!categories.value || !categories.value[0] || !categories.value[0].monthlySums) {
console.warn('monthlySums отсутствует или данные не загружены');
return []; // или null, или другое значение по умолчанию
}
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({
responsive: true,
plugins: {
legend: {
display: true,
position: "top",
},
title: {
display: true,
text: "График расходов по категориям",
},
},
scales: {
x: {
title: {
display: true,
text: "Месяц",
},
},
y: {
title: {
display: true,
text: "Сумма",
},
},
},
});
// Преобразование данных для таблицы // Преобразование данных для таблицы
const prepareTableData = (categories) => { const prepareTableData = (categories) => {
const allMonths = [ // 1. Собираем все уникальные значения date из monthlySums
const allDates = [
...new Set( ...new Set(
categories.flatMap((category) => categories.flatMap((category) =>
category.monthlyData.map((monthData) => monthData.month) category.monthlySums.map((sumItem) => sumItem.date)
) )
), ),
]; ];
// 2. Сортируем даты.
// Если у вас формат "YYYY-MM-DD", лексикографическая сортировка работает корректно хронологически:
allDates.sort((a, b) => a.localeCompare(b));
// (Если хотите гарантированно использовать объекты Date, можно так:
// allDates.sort((a, b) => new Date(a) - new Date(b));
// Но тогда поля колонки будут тоже строками вида "2024-09-01" — обычно это ок.)
// 3. Формируем колонки для DataTable:
// - Первый столбец: "category"
// - Далее по одному столбцу на каждую дату
tableColumns.value = [ tableColumns.value = [
{ field: "category", header: "Категория" }, { field: "category", header: "Категория" },
...allMonths.map((month) => ({ field: month, header: month })), ...allDates.map((dateStr) => ({ field: dateStr, header: dateStr })),
]; // Устанавливаем столбцы для DataTable ];
console.log(tableColumns.value[0].field);
// 4. Формируем строки (для каждой категории)
return categories.map((category) => { return categories.map((category) => {
const row = { category: category.categoryName }; // Начинаем со строки, где есть поле с именем категории
allMonths.forEach((month) => { const row = {category: category.categoryIcon + " "+category.categoryName};
const data = category.monthlyData.find((m) => m.month === month);
row[month] = data ? data.totalAmount : 0; // Для каждой даты проверяем, есть ли в monthlySums соответствующая запись
allDates.forEach((dateStr) => {
const found = category.monthlySums.find((m) => m.date === dateStr);
if (found.difference != 0) {
if (found.difference > 0) {
row[dateStr] = found ? found.total + " (+ " + found.difference + "%)" : 0;
} else {
row[dateStr] = found ? found.total + " (" + found.difference + "%)" : 0;
}
} else {
row[dateStr] = found ? found.total : 0;
}
}); });
return row; return row;
}); });
}; };
const fetchCategoriesSums = async () => { const fetchCategoriesSums = async () => {
loading.value = true; loading.value = true;
await getCategoriesSumsRequest().then((data) => { await getCategoriesSumsRequest().then((data) => {
categories.value = data.data; categories.value = data.data;
console.log(categories.value);
dataTableCategories.value = prepareTableData(data.data); dataTableCategories.value = prepareTableData(data.data);
}); });
loading.value = false; loading.value = false;
}; };
onMounted(async () => { onMounted(async () => {
loading.value = true;
await fetchCategoriesSums(); await fetchCategoriesSums();
loading.value = false;
}); });
</script> </script>
<template> <template>
<LoadingView v-if="loading" /> <LoadingView v-if="loading" />
<div v-else class="px-4 bg-gray-100 h-full flex flex-col gap-4"> <div v-else class="p-4 bg-gray-100 h-full flex flex-col gap-4 items-center justify-items-center ">
<!-- Таблица с преобразованными данными --> <!-- Таблица с преобразованными данными -->
<DataTable :value="dataTableCategories" responsiveLayout="scroll"> <!-- {{// chartData}}-->
<Column v-for="col in tableColumns" :key="col.field" :field="col.field" :header="col.header" /> <!-- <Chart type="line" :data="chartData" :options="chartOptions" />-->
{{tableColumns[0].field}}
<DataTable :value="dataTableCategories" responsiveLayout="scroll" stripedRows class="w-5/6 items-center">
<Column
:field="tableColumns[0].field"
:header="tableColumns[0].header"
:bodyCellClass="'bold-column'"
:headerCellClass="'bold-column'"
class="font-bold"
/>
<!-- Остальные колонки -->
<Column
v-for="(col, index) in tableColumns.slice(1)"
:key="col.field"
:field="col.field"
:header="col.header"
/>
</DataTable> </DataTable>
</div> </div>
</template> </template>

View File

@@ -4,8 +4,8 @@ import { useRouter } from 'vue-router';
// Создаем экземпляр axios // Создаем экземпляр axios
const api = axios.create({ const api = axios.create({
baseURL: 'https://luminic.space/api/', // baseURL: 'https://luminic.space/api/',
// baseURL: 'http://localhost:8082/api', baseURL: 'http://localhost:8082/api',
}); });
// Устанавливаем токен из localStorage при каждом запуске // Устанавливаем токен из localStorage при каждом запуске

View File

@@ -25,6 +25,6 @@ export const deleteCategory = async (id: number) => {
}; };
export const getCategoriesSumsRequest = async () => { export const getCategoriesSumsRequest = async () => {
return await apiClient.get('/categories/by-month'); return await apiClient.get('/categories/by-month2');
} }