analytics + categories sort in creation + auto focus on sum
This commit is contained in:
@@ -1,261 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import LoadingView from "@/components/LoadingView.vue";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {getTransactionCategoriesSums} from "@/services/transactionService";
|
||||
import Chart from "primevue/chart";
|
||||
import MultiSelect from "primevue/multiselect";
|
||||
import {format} from "date-fns";
|
||||
import {generateRandomColors} from "@/utils/utils";
|
||||
import {Category} from "@/models/Category";
|
||||
import {getCategories} from "@/services/categoryService";
|
||||
import {Chart as ChartJS} from "chart.js/auto";
|
||||
import ChartDataLabels from "chartjs-plugin-datalabels";
|
||||
|
||||
ChartJS.register(ChartDataLabels);
|
||||
|
||||
import {getCategories, getCategoriesSumsRequest} from "@/services/categoryService";
|
||||
import DataTable from "primevue/datatable";
|
||||
import Column from "primevue/column";
|
||||
|
||||
const loading = ref(false);
|
||||
const categoriesSums = ref([])
|
||||
const dataTableCategories = ref([])
|
||||
const categories = ref([]);
|
||||
const dataTableCategories = ref([]);
|
||||
const tableColumns = ref([]);
|
||||
|
||||
// Преобразование данных для таблицы
|
||||
const prepareTableData = (categories) => {
|
||||
const allMonths = [
|
||||
...new Set(
|
||||
categories.flatMap((category) =>
|
||||
category.monthlyData.map((monthData) => monthData.month)
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
tableColumns.value = [
|
||||
{ field: "category", header: "Категория" },
|
||||
...allMonths.map((month) => ({ field: month, header: month })),
|
||||
]; // Устанавливаем столбцы для DataTable
|
||||
|
||||
return categories.map((category) => {
|
||||
const row = { category: category.categoryName };
|
||||
allMonths.forEach((month) => {
|
||||
const data = category.monthlyData.find((m) => m.month === month);
|
||||
row[month] = data ? data.totalAmount : 0;
|
||||
});
|
||||
return row;
|
||||
});
|
||||
};
|
||||
|
||||
const fetchCategoriesSums = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
categoriesSums.value = await getTransactionCategoriesSums()
|
||||
// console.log(categoriesSums.value)
|
||||
for (let category of categoriesSums.value) {
|
||||
// console.log(category)
|
||||
// [{
|
||||
// "category": category[0],
|
||||
// "category"+[category[1]]: category[2]
|
||||
// }]
|
||||
// console.log('test')
|
||||
// console.log('dataTableCategories '+ dataTableCategories.value)
|
||||
let categoryInList = dataTableCategories.value.find((listCategory: Category) => {
|
||||
// console.log(listCategory['category'].id)
|
||||
// console.log(category[0].id)
|
||||
return listCategory['category'].id === category[0].id
|
||||
})
|
||||
console.log('cat in list ' + categoryInList)
|
||||
if (categoryInList) {
|
||||
console.log('cat[1] '+ category[1])
|
||||
console.log('cat[2] '+ category[2])
|
||||
categoryInList[category[1]] = category[2]
|
||||
// console.log(categoryInList)
|
||||
} else {
|
||||
dataTableCategories.value.push({'category': category[0]})
|
||||
dataTableCategories.value.filter((listCategory: Category) => {
|
||||
return listCategory['category'].id === category.id
|
||||
})[category[1]] = category[2]
|
||||
}
|
||||
// console.log(categoryInList)
|
||||
}
|
||||
// console.log(dataTableCategories.value)
|
||||
// console.log(categoriesSums.value)
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories sums:', error);
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const categories = ref<Category[]>([])
|
||||
const selectedCategories = ref([])
|
||||
|
||||
const fetchCategories = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await getCategories('EXPENSE');
|
||||
categories.value = response.data
|
||||
console.log(categories.value.filter(i => i.id == 30))
|
||||
selectedCategories.value.push(categories.value.filter(i => i.id == 30)[0])
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error);
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const chartData = computed(() => {
|
||||
return {
|
||||
labels: chartLabels.value[0],
|
||||
datasets: chartLabels.value[1]
|
||||
};
|
||||
})
|
||||
const chartOptions = computed(() => {
|
||||
return {
|
||||
maintainAspectRatio: false,
|
||||
aspectRatio: 0.6,
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: 'rgba(0, 0, 0, 1)',
|
||||
font: {
|
||||
weight: 'bold',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(255, 0, 0, 0.5)',
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
const chartLabels = computed(() => {
|
||||
let dates = new Array<Date>()
|
||||
categoriesSums.value.filter(i => i[0].id == 30).forEach((item) => {
|
||||
dates.push(format(new Date(item[1]), 'MM.yy'))
|
||||
})
|
||||
|
||||
|
||||
let datasets = []
|
||||
|
||||
categoriesSums.value.forEach((item) => {
|
||||
// Проверка, есть ли категория в массиве выбранных категорий `selectedCategories`
|
||||
const isSelected = selectedCategories.value.some((category) => category.id == item[0].id);
|
||||
|
||||
if (!isSelected) {
|
||||
return; // Пропускаем категории, которых нет в `selectedCategories`
|
||||
}
|
||||
|
||||
// Проверка, есть ли уже категория с таким названием в `datasets`
|
||||
const existingDataset = datasets.find((v) => v.label === item[0].name);
|
||||
let color = generateRandomColors();
|
||||
|
||||
if (!existingDataset) {
|
||||
// Создаем новый объект `dataset` для новой категории
|
||||
let dataset = {
|
||||
label: item[0].name,
|
||||
data: categoriesSums.value
|
||||
.filter((i) => i[0].id === item[0].id)
|
||||
.map((value) => value[2]), // Собираем массив значений для категории
|
||||
fill: true,
|
||||
borderColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 1)`,
|
||||
backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.2)`,
|
||||
tension: 0.4,
|
||||
};
|
||||
|
||||
// Добавляем созданный объект `dataset` в массив `datasets`
|
||||
datasets.push(dataset);
|
||||
}
|
||||
loading.value = true;
|
||||
await getCategoriesSumsRequest().then((data) => {
|
||||
categories.value = data.data;
|
||||
dataTableCategories.value = prepareTableData(data.data);
|
||||
});
|
||||
|
||||
return [dates, datasets];
|
||||
|
||||
})
|
||||
|
||||
// const categories = computed(() => {
|
||||
// let categoriesIds = new Array<number>()
|
||||
// categoriesSums.value.forEach((i) => {
|
||||
// categoriesIds.push(i[0].id)
|
||||
// })
|
||||
// return categoriesIds
|
||||
// })
|
||||
|
||||
|
||||
const setChartData = () => {
|
||||
console.log(chartLabels.value[1])
|
||||
return {
|
||||
labels: chartLabels.value[0],
|
||||
datasets: chartLabels.value[1]
|
||||
};
|
||||
}
|
||||
|
||||
const setChartOptions = () => {
|
||||
// const documentStyle = getComputedStyle(document.documentElement);
|
||||
// const textColor = documentStyle.getPropertyValue('--p-text-color');
|
||||
// const textColorSecondary = documentStyle.getPropertyValue('--p-text-muted-color');
|
||||
// const surfaceBorder = documentStyle.getPropertyValue('--p-content-border-color');
|
||||
|
||||
return {
|
||||
maintainAspectRatio: false,
|
||||
aspectRatio: 0.6,
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: 'rgba(0, 0, 0, 1)',
|
||||
font: {
|
||||
weight: 'bold'
|
||||
}
|
||||
}
|
||||
},
|
||||
datalabels: {
|
||||
color: 'rgba(0, 0, 0, 1)', // Цвет текста
|
||||
anchor: 'end', // Привязка метки
|
||||
align: 'top', // Выравнивание над точкой
|
||||
offset: 8, // Отступ от точки
|
||||
labels: {
|
||||
|
||||
font: {
|
||||
weight: 'bold'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(255, 0, 0, 0.5)',
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
await Promise.all([fetchCategoriesSums(), fetchCategories()])
|
||||
loading.value = false
|
||||
|
||||
})
|
||||
await fetchCategoriesSums();
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<LoadingView v-if="loading"/>
|
||||
<div v-else class="px-4 bg-gray-100 h-full flex flex-col gap-4 ">
|
||||
<MultiSelect v-model="selectedCategories" :options="categories" optionLabel="name" filter
|
||||
placeholder="Выберите категории"
|
||||
:maxSelectedLabels="3" class="w-full md:w-80"/>
|
||||
|
||||
<Chart v-if="selectedCategories.length > 0" type="line" :data="chartData" :options="chartOptions"
|
||||
class="h-[30rem]"/>
|
||||
|
||||
{{dataTableCategories}}
|
||||
<DataTable :value="dataTableCategories">
|
||||
<!-- <Column v-for="dataTableCategories"/>-->
|
||||
<LoadingView v-if="loading" />
|
||||
<div v-else class="px-4 bg-gray-100 h-full flex flex-col gap-4">
|
||||
<!-- Таблица с преобразованными данными -->
|
||||
<DataTable :value="dataTableCategories" responsiveLayout="scroll">
|
||||
<Column v-for="col in tableColumns" :key="col.field" :field="col.field" :header="col.header" />
|
||||
</DataTable>
|
||||
<!-- {{categories}}-->
|
||||
<!-- {{// chartData}}-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user