+analytics update
This commit is contained in:
@@ -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));
|
||||||
@@ -90,18 +132,19 @@ const prepareTableData = (categories) => {
|
|||||||
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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user