From b483674ebe14dcf91a3dab5404a644a564193dd1 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 6 Jun 2026 23:00:28 +0200 Subject: [PATCH] refactor(libra): collapse type filter + voided switch into category chips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each chip toggles inclusion of one bucket of rows. Every row belongs to exactly one bucket — voided rows go to the Voided bucket regardless of their underlying type — so the model is straightforward: [Income] [Expenses] [Voided] | | | income expense voided (non-voided only) (any type) Defaults: Income + Expenses on, Voided off. Independent multi-select. Empty selection shows the empty state with a 'select a category' hint instead of an open-ended 'try a different time period'. Replaces the previous 'type radio + voided switch' pair: same axes, one control type, no left/right visual split. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../expenses/views/TransactionsPage.vue | 83 +++++++++---------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/src/modules/expenses/views/TransactionsPage.vue b/src/modules/expenses/views/TransactionsPage.vue index 7a34476..fafee56 100644 --- a/src/modules/expenses/views/TransactionsPage.vue +++ b/src/modules/expenses/views/TransactionsPage.vue @@ -11,8 +11,6 @@ import FuzzySearch from '@/components/ui/fuzzy-search/FuzzySearch.vue' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' -import { Switch } from '@/components/ui/switch' -import { Label } from '@/components/ui/label' import { CheckCircle2, Clock, @@ -32,15 +30,26 @@ const isLoading = ref(false) const dateRangeType = ref(15) // 15, 30, 60, or 'custom' const customStartDate = ref('') const customEndDate = ref('') -const typeFilter = ref<'all' | 'income' | 'expense'>('all') -const showVoided = ref(false) +// Each chip is an inclusion toggle for one bucket of rows. Every row +// belongs to exactly one bucket (voided rows go to 'voided' regardless +// of their income/expense type). Default hides voided. +type Category = 'income' | 'expense' | 'voided' -const typeFilterOptions = [ - { label: 'All', value: 'all' as const }, - { label: 'Income', value: 'income' as const }, - { label: 'Expenses', value: 'expense' as const } +const activeCategories = ref>(new Set(['income', 'expense'])) + +const categoryChips: { label: string; value: Category }[] = [ + { label: 'Income', value: 'income' }, + { label: 'Expenses', value: 'expense' }, + { label: 'Voided', value: 'voided' } ] +function toggleCategory(cat: Category) { + const next = new Set(activeCategories.value) + if (next.has(cat)) next.delete(cat) + else next.add(cat) + activeCategories.value = next +} + function isIncome(t: Transaction): boolean { return t.tags?.includes('income-entry') ?? false } @@ -57,6 +66,14 @@ function isPending(t: Transaction): boolean { return t.flag === '!' && !isVoided(t) } +// Which chip bucket a row falls into. Voided always wins over type. +function getBucket(t: Transaction): Category | null { + if (isVoided(t)) return 'voided' + if (isIncome(t)) return 'income' + if (isExpense(t)) return 'expense' + return null +} + const walletKey = computed(() => user.value?.wallets?.[0]?.inkey) // Fuzzy search state and configuration @@ -82,23 +99,13 @@ const searchOptions: FuzzySearchOptions = { matchAllWhenSearchEmpty: true } -// Transactions to display: search results (or all), with voided hidden by -// default and the type filter applied last. +// Transactions to display: row passes if its bucket's chip is active. const transactionsToDisplay = computed(() => { - let base = searchResults.value.length > 0 ? searchResults.value : transactions.value - if (!showVoided.value) base = base.filter(t => !isVoided(t)) - if (typeFilter.value === 'income') return base.filter(isIncome) - if (typeFilter.value === 'expense') return base.filter(isExpense) - return base -}) - -// Count of voided entries that are being hidden right now — surfaced in -// the results-count line so the user knows the toggle has something to -// reveal. -const voidedHiddenCount = computed(() => { - if (showVoided.value) return 0 const base = searchResults.value.length > 0 ? searchResults.value : transactions.value - return base.filter(isVoided).length + return base.filter(t => { + const bucket = getBucket(t) + return bucket !== null && activeCategories.value.has(bucket) + }) }) // Handle search results @@ -273,30 +280,21 @@ onMounted(() => { - +
-
- - -
@@ -352,9 +350,6 @@ onMounted(() => { {{ transactionsToDisplay.length }} transaction{{ transactionsToDisplay.length === 1 ? '' : 's' }} - - ({{ voidedHiddenCount }} voided hidden) - @@ -369,7 +364,9 @@ onMounted(() => {

No transactions found

- {{ searchResults.length > 0 ? 'Try a different search term' : 'Try selecting a different time period' }} + + +