From 83de5fed3423022aba4bfb4485e085a8906a981a Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Wed, 4 Feb 2026 16:57:44 +0100 Subject: [PATCH] cospend: filter recent activity by chart category selection Clicking a category on the bar chart now filters the recent activity list to show only payments in that category. Includes a clear filter button and empty state message. Also increases recent splits from 10 to 30 for better coverage when filtering. --- src/lib/components/BarChart.svelte | 18 +++++- src/routes/api/cospend/balance/+server.ts | 2 +- src/routes/cospend/+page.svelte | 78 ++++++++++++++++++++++- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/lib/components/BarChart.svelte b/src/lib/components/BarChart.svelte index 79facab..fa48336 100644 --- a/src/lib/components/BarChart.svelte +++ b/src/lib/components/BarChart.svelte @@ -2,7 +2,7 @@ import { onMount } from 'svelte'; import { Chart, registerables } from 'chart.js'; - let { data = { labels: [], datasets: [] }, title = '', height = '400px' } = $props<{ data?: any, title?: string, height?: string }>(); + let { data = { labels: [], datasets: [] }, title = '', height = '400px', onFilterChange = null } = $props<{ data?: any, title?: string, height?: string, onFilterChange?: ((categories: string[] | null) => void) | null }>(); let canvas = $state(); let chart = $state(); @@ -42,6 +42,19 @@ return categoryColorMap[category] || nordColors[index % nordColors.length]; } + function emitFilter() { + if (!onFilterChange || !chart) return; + const allVisible = chart.data.datasets.every((_, idx) => !chart.getDatasetMeta(idx).hidden); + if (allVisible) { + onFilterChange(null); + } else { + const visible = chart.data.datasets + .filter((_, idx) => !chart.getDatasetMeta(idx).hidden) + .map(ds => ds.label.toLowerCase()); + onFilterChange(visible); + } + } + function createChart() { if (!canvas || !data.datasets) return; @@ -135,7 +148,6 @@ }, onClick: (event, legendItem, legend) => { const datasetIndex = legendItem.datasetIndex; - const clickedMeta = chart.getDatasetMeta(datasetIndex); // Check if only this dataset is currently visible const onlyThisVisible = chart.data.datasets.every((dataset, idx) => { @@ -156,6 +168,7 @@ } chart.update(); + emitFilter(); } }, title: { @@ -229,6 +242,7 @@ } chart.update(); + emitFilter(); } } }, diff --git a/src/routes/api/cospend/balance/+server.ts b/src/routes/api/cospend/balance/+server.ts index 39f5880..38719eb 100644 --- a/src/routes/api/cospend/balance/+server.ts +++ b/src/routes/api/cospend/balance/+server.ts @@ -89,7 +89,7 @@ export const GET: RequestHandler = async ({ locals, url }) => { }, { $unwind: '$paymentId' }, { $sort: { 'paymentId.date': -1, 'paymentId.createdAt': -1 } }, - { $limit: 10 } + { $limit: 30 } ]); // For settlements, fetch the other user's split info diff --git a/src/routes/cospend/+page.svelte b/src/routes/cospend/+page.svelte index a42fa75..c3b3ab6 100644 --- a/src/routes/cospend/+page.svelte +++ b/src/routes/cospend/+page.svelte @@ -24,6 +24,13 @@ let error = $state(null); let monthlyExpensesData = $state(data.monthlyExpensesData || { labels: [], datasets: [] }); let expensesLoading = $state(false); + let categoryFilter = $state(null); + + let filteredSplits = $derived( + categoryFilter + ? (balance.recentSplits || []).filter(split => categoryFilter.includes(split.paymentId?.category)) + : balance.recentSplits || [] + ); // Component references for refreshing let enhancedBalanceComponent; @@ -177,6 +184,7 @@ data={monthlyExpensesData} title="Monthly Expenses by Category" height="400px" + onFilterChange={(categories) => categoryFilter = categories} /> {:else}
@@ -194,9 +202,17 @@
Error: {error}
{:else if balance.recentSplits && balance.recentSplits.length > 0}
-

Recent Activity

+
+

Recent Activity{#if categoryFilter} — {categoryFilter.map(c => getCategoryName(c)).join(', ')}{/if}

+ {#if categoryFilter} + + {/if} +
+ {#if filteredSplits.length === 0} +

No recent activity in {categoryFilter.map(c => getCategoryName(c)).join(', ')}.

+ {/if}
- {#each balance.recentSplits as split} + {#each filteredSplits as split} {#if isSettlementPayment(split.paymentId)}