feat: Introduce DashboardWeek and WeekCategory models to display pre-calculated weekly expense data in dashboard charts.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import Chart from 'primevue/chart';
|
import Chart from 'primevue/chart';
|
||||||
import { DashboardCategory } from '@/models/dashboard';
|
import { DashboardCategory, DashboardWeek } from '@/models/dashboard';
|
||||||
import { Transaction } from '@/models/transaction';
|
import { Transaction } from '@/models/transaction';
|
||||||
import { TransactionType } from '@/models/enums';
|
import { TransactionType } from '@/models/enums';
|
||||||
import { formatAmount } from '@/utils/utils';
|
import { formatAmount } from '@/utils/utils';
|
||||||
@@ -13,6 +13,7 @@ dayjs.extend(isoWeek);
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
categories: DashboardCategory[];
|
categories: DashboardCategory[];
|
||||||
transactions: Transaction[];
|
transactions: Transaction[];
|
||||||
|
weeks: DashboardWeek[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const chartType = ref<'category' | 'weekly'>('category');
|
const chartType = ref<'category' | 'weekly'>('category');
|
||||||
@@ -84,56 +85,14 @@ const getCategoryColor = (index: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- Weekly Chart Logic ---
|
// --- Weekly Chart Logic ---
|
||||||
interface WeekData {
|
|
||||||
label: string;
|
|
||||||
start: dayjs.Dayjs;
|
|
||||||
end: dayjs.Dayjs;
|
|
||||||
amount: number;
|
|
||||||
transactions: Transaction[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const weeklyData = computed(() => {
|
|
||||||
const weeks: WeekData[] = [];
|
|
||||||
// Generate last 4 weeks
|
|
||||||
for (let i = 3; i >= 0; i--) {
|
|
||||||
const startOfWeek = dayjs().subtract(i, 'week').startOf('isoWeek');
|
|
||||||
const endOfWeek = dayjs().subtract(i, 'week').endOf('isoWeek');
|
|
||||||
weeks.push({
|
|
||||||
label: `${startOfWeek.format('D MMM')} - ${endOfWeek.format('D MMM')}`,
|
|
||||||
start: startOfWeek,
|
|
||||||
end: endOfWeek,
|
|
||||||
amount: 0,
|
|
||||||
transactions: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter expense transactions from the transactions prop
|
|
||||||
const expenseTransactions = props.transactions;
|
|
||||||
|
|
||||||
expenseTransactions.forEach(t => {
|
|
||||||
const tDate = dayjs(t.date);
|
|
||||||
const week = weeks.find(w => tDate.isAfter(w.start.subtract(1, 'second')) && tDate.isBefore(w.end.add(1, 'second')));
|
|
||||||
if (week) {
|
|
||||||
week.amount += t.amount;
|
|
||||||
week.transactions.push(t);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sort transactions by amount descending
|
|
||||||
weeks.forEach(week => {
|
|
||||||
week.transactions.sort((a, b) => b.amount - a.amount);
|
|
||||||
});
|
|
||||||
|
|
||||||
return weeks;
|
|
||||||
});
|
|
||||||
|
|
||||||
const weeklyChartData = computed(() => {
|
const weeklyChartData = computed(() => {
|
||||||
return {
|
return {
|
||||||
labels: weeklyData.value.map(w => w.label),
|
labels: props.weeks.map(w => `${dayjs(w.startDate).format('D MMM')} - ${dayjs(w.endDate).format('D MMM')}`),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Expenses',
|
label: 'Expenses',
|
||||||
data: weeklyData.value.map(w => w.amount),
|
data: props.weeks.map(w => w.expenseSum),
|
||||||
backgroundColor: '#3b82f6',
|
backgroundColor: '#3b82f6',
|
||||||
borderRadius: 8
|
borderRadius: 8
|
||||||
}
|
}
|
||||||
@@ -232,27 +191,28 @@ const weeklyChartOptions = computed(() => {
|
|||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div v-for="week in weeklyData" :key="week.label" class="flex flex-col gap-2">
|
<div v-for="week in props.weeks" :key="week.startDate.toString()" class="flex flex-col gap-2">
|
||||||
<div class="flex items-center justify-between p-2 bg-surface-50 dark:bg-surface-800 rounded-lg">
|
<div class="flex items-center justify-between p-2 bg-surface-50 dark:bg-surface-800 rounded-lg">
|
||||||
<span class="font-medium text-surface-700 dark:text-surface-200">{{ week.label }}</span>
|
<span class="font-medium text-surface-700 dark:text-surface-200">{{
|
||||||
<span class="font-semibold text-surface-900 dark:text-surface-0">{{ formatAmount(week.amount) }}
|
dayjs(week.startDate).format('D MMM') }} - {{ dayjs(week.endDate).format('D MMM') }}</span>
|
||||||
|
<span class="font-semibold text-surface-900 dark:text-surface-0">{{ formatAmount(week.expenseSum) }}
|
||||||
₽</span>
|
₽</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Transactions List -->
|
<!-- Categories List -->
|
||||||
<div
|
<div
|
||||||
class="flex flex-col gap-1 pl-2 border-l-2 border-surface-100 dark:border-surface-700 ml-2 h-fit">
|
class="flex flex-col gap-1 pl-2 border-l-2 border-surface-100 dark:border-surface-700 ml-2 h-fit">
|
||||||
<div v-for="tx in week.transactions.slice(0, 5)" :key="tx.id"
|
<div v-for="cat in week.categories.slice(0, 5)" :key="cat.categoryId"
|
||||||
class="flex items-center justify-between py-1 px-2 text-sm hover:bg-surface-50 dark:hover:bg-surface-800 rounded transition-colors">
|
class="flex items-center justify-between py-1 px-2 text-sm hover:bg-surface-50 dark:hover:bg-surface-800 rounded transition-colors">
|
||||||
<div class="flex items-center gap-2 overflow-hidden">
|
<div class="flex items-center gap-2 overflow-hidden">
|
||||||
<span class="text-lg">{{ tx.category.icon }}</span>
|
<span class="text-lg">{{ cat.categoryIcon }}</span>
|
||||||
<span class="text-surface-600 dark:text-surface-300 truncate">{{ tx.category.name }}</span>
|
<span class="text-surface-600 dark:text-surface-300 truncate">{{ cat.categoryName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="font-medium text-surface-900 dark:text-surface-100 whitespace-nowrap">{{
|
<span class="font-medium text-surface-900 dark:text-surface-100 whitespace-nowrap">{{
|
||||||
formatAmount(tx.amount) }} ₽</span>
|
cat.sum ? formatAmount(cat.sum) : 0 }} ₽</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="week.transactions.length > 5" class="text-xs text-surface-400 pl-2 pt-1">
|
<div v-if="week.categories.length > 5" class="text-xs text-surface-400 pl-2 pt-1">
|
||||||
+{{ week.transactions.length - 5 }} more
|
+{{ week.categories.length - 5 }} more
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -161,7 +161,8 @@ const userName = computed(() => {
|
|||||||
<!-- Charts & Upcoming -->
|
<!-- Charts & Upcoming -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-1 gap-4">
|
<div class="grid grid-cols-1 lg:grid-cols-1 gap-4">
|
||||||
<!-- Charts -->
|
<!-- Charts -->
|
||||||
<DashboardCharts :categories="dashboardData.categories" :transactions="dashboardTransactions" />
|
<DashboardCharts :categories="dashboardData.categories" :transactions="dashboardTransactions"
|
||||||
|
:weeks="dashboardData.weeks" />
|
||||||
|
|
||||||
<!-- Upcoming Transactions -->
|
<!-- Upcoming Transactions -->
|
||||||
<div class=" ">
|
<div class=" ">
|
||||||
|
|||||||
@@ -8,6 +8,21 @@ export interface DashboardData {
|
|||||||
categories: DashboardCategory[],
|
categories: DashboardCategory[],
|
||||||
upcomingTransactions: Transaction[],
|
upcomingTransactions: Transaction[],
|
||||||
recentTransactions: Transaction[],
|
recentTransactions: Transaction[],
|
||||||
|
weeks: DashboardWeek[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardWeek {
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date,
|
||||||
|
expenseSum: number,
|
||||||
|
categories: WeekCategory[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WeekCategory {
|
||||||
|
categoryId: number,
|
||||||
|
categoryName: string,
|
||||||
|
categoryIcon: string
|
||||||
|
sum: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DashboardCategory {
|
export interface DashboardCategory {
|
||||||
@@ -17,3 +32,4 @@ export interface DashboardCategory {
|
|||||||
changeDiff: number,
|
changeDiff: number,
|
||||||
changeDiffPercentage: number,
|
changeDiffPercentage: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user