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"> <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>

View File

@@ -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=" ">

View File

@@ -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,
} }