feat: Introduce DashboardWeek and WeekCategory models to display pre-calculated weekly expense data in dashboard charts.

This commit is contained in:
xds
2025-11-21 19:58:36 +03:00
parent f3286d2f4c
commit f6f3a1b70c
3 changed files with 33 additions and 56 deletions

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import Chart from 'primevue/chart';
import { DashboardCategory } from '@/models/dashboard';
import { DashboardCategory, DashboardWeek } from '@/models/dashboard';
import { Transaction } from '@/models/transaction';
import { TransactionType } from '@/models/enums';
import { formatAmount } from '@/utils/utils';
@@ -13,6 +13,7 @@ dayjs.extend(isoWeek);
const props = defineProps<{
categories: DashboardCategory[];
transactions: Transaction[];
weeks: DashboardWeek[];
}>();
const chartType = ref<'category' | 'weekly'>('category');
@@ -84,56 +85,14 @@ const getCategoryColor = (index: number) => {
};
// --- 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(() => {
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: [
{
label: 'Expenses',
data: weeklyData.value.map(w => w.amount),
data: props.weeks.map(w => w.expenseSum),
backgroundColor: '#3b82f6',
borderRadius: 8
}
@@ -232,27 +191,28 @@ const weeklyChartOptions = computed(() => {
<template v-else>
<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">
<span class="font-medium text-surface-700 dark:text-surface-200">{{ week.label }}</span>
<span class="font-semibold text-surface-900 dark:text-surface-0">{{ formatAmount(week.amount) }}
<span class="font-medium text-surface-700 dark:text-surface-200">{{
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>
</div>
<!-- Transactions List -->
<!-- Categories List -->
<div
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">
<div class="flex items-center gap-2 overflow-hidden">
<span class="text-lg">{{ tx.category.icon }}</span>
<span class="text-surface-600 dark:text-surface-300 truncate">{{ tx.category.name }}</span>
<span class="text-lg">{{ cat.categoryIcon }}</span>
<span class="text-surface-600 dark:text-surface-300 truncate">{{ cat.categoryName }}</span>
</div>
<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 v-if="week.transactions.length > 5" class="text-xs text-surface-400 pl-2 pt-1">
+{{ week.transactions.length - 5 }} more
<div v-if="week.categories.length > 5" class="text-xs text-surface-400 pl-2 pt-1">
+{{ week.categories.length - 5 }} more
</div>
</div>
</div>

View File

@@ -161,7 +161,8 @@ const userName = computed(() => {
<!-- Charts & Upcoming -->
<div class="grid grid-cols-1 lg:grid-cols-1 gap-4">
<!-- Charts -->
<DashboardCharts :categories="dashboardData.categories" :transactions="dashboardTransactions" />
<DashboardCharts :categories="dashboardData.categories" :transactions="dashboardTransactions"
:weeks="dashboardData.weeks" />
<!-- Upcoming Transactions -->
<div class=" ">

View File

@@ -8,6 +8,21 @@ export interface DashboardData {
categories: DashboardCategory[],
upcomingTransactions: 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 {
@@ -17,3 +32,4 @@ export interface DashboardCategory {
changeDiff: number,
changeDiffPercentage: number,
}