feat(libra): add income/expense type filter to transaction history

All / Income / Expenses toggle row with a Filter icon, aligned to the
Calendar-iconed date range row above. Filters transactionsToDisplay
client-side using the existing isIncome/isExpense helpers; works on
top of the search and date filters.
This commit is contained in:
Padreug 2026-05-16 23:25:59 +02:00
commit 4fee9c015d

View file

@ -17,7 +17,8 @@ import {
Flag, Flag,
XCircle, XCircle,
RefreshCw, RefreshCw,
Calendar Calendar,
Filter
} from 'lucide-vue-next' } from 'lucide-vue-next'
const { user } = useAuth() const { user } = useAuth()
@ -29,6 +30,13 @@ const isLoading = ref(false)
const dateRangeType = ref<number | 'custom'>(15) // 15, 30, 60, or 'custom' const dateRangeType = ref<number | 'custom'>(15) // 15, 30, 60, or 'custom'
const customStartDate = ref<string>('') const customStartDate = ref<string>('')
const customEndDate = ref<string>('') const customEndDate = ref<string>('')
const typeFilter = ref<'all' | 'income' | 'expense'>('all')
const typeFilterOptions = [
{ label: 'All', value: 'all' as const },
{ label: 'Income', value: 'income' as const },
{ label: 'Expenses', value: 'expense' as const }
]
function isIncome(t: Transaction): boolean { function isIncome(t: Transaction): boolean {
return t.tags?.includes('income-entry') ?? false return t.tags?.includes('income-entry') ?? false
@ -63,9 +71,12 @@ const searchOptions: FuzzySearchOptions<Transaction> = {
matchAllWhenSearchEmpty: true matchAllWhenSearchEmpty: true
} }
// Transactions to display (search results or all transactions) // Transactions to display (search results or all transactions), filtered by type
const transactionsToDisplay = computed(() => { const transactionsToDisplay = computed(() => {
return searchResults.value.length > 0 ? searchResults.value : transactions.value const base = searchResults.value.length > 0 ? searchResults.value : transactions.value
if (typeFilter.value === 'income') return base.filter(isIncome)
if (typeFilter.value === 'expense') return base.filter(isExpense)
return base
}) })
// Handle search results // Handle search results
@ -213,6 +224,22 @@ onMounted(() => {
</Button> </Button>
</div> </div>
<!-- Type Filter (All / Income / Expenses) -->
<div class="flex items-center gap-2 flex-wrap">
<Filter class="h-4 w-4 text-muted-foreground" />
<Button
v-for="option in typeFilterOptions"
:key="option.value"
:variant="typeFilter === option.value ? 'default' : 'outline'"
size="sm"
class="h-8 px-3 text-xs"
@click="typeFilter = option.value"
:disabled="isLoading"
>
{{ option.label }}
</Button>
</div>
<!-- Custom Date Range Inputs --> <!-- Custom Date Range Inputs -->
<div v-if="dateRangeType === 'custom'" class="flex items-end gap-2 flex-wrap"> <div v-if="dateRangeType === 'custom'" class="flex items-end gap-2 flex-wrap">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">