+analytics
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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 при каждом запуске
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user