Compare commits

...

4 commits

Author SHA1 Message Date
9252c86f5c feat(libra): shrink filter controls on mobile
Drop the date-range/type-filter buttons and custom-date inputs from
h-8 to h-7 below md to claw back a bit of vertical space on phones,
keeping h-8 on tablet/desktop where it's comfortable.
2026-05-16 23:28:29 +02:00
22ad5fd974 feat(libra): drop User and Source rows from transaction cards
User row is noise on a personal history view, and Source is always
libra-api right now — both just clutter the card. Drop them; can
bring back if/when there's a second source to disambiguate.
2026-05-16 23:27:41 +02:00
8b91c7bd72 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.
2026-05-16 23:25:59 +02:00
396b012707 feat(libra): color-code income/expense entries in transaction history
Left green/red stripe on each card plus a matching tint on the
income-entry / expense-entry tag badge — mirrors the Record page's
red/green palette so the two screens read consistently.
2026-05-16 23:24:20 +02:00

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,21 @@ 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 {
return t.tags?.includes('income-entry') ?? false
}
function isExpense(t: Transaction): boolean {
return t.tags?.includes('expense-entry') ?? false
}
const walletKey = computed(() => user.value?.wallets?.[0]?.inkey) const walletKey = computed(() => user.value?.wallets?.[0]?.inkey)
@ -55,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
@ -197,7 +216,7 @@ onMounted(() => {
:key="option.value" :key="option.value"
:variant="dateRangeType === option.value ? 'default' : 'outline'" :variant="dateRangeType === option.value ? 'default' : 'outline'"
size="sm" size="sm"
class="h-8 px-3 text-xs" class="h-7 md:h-8 px-3 text-xs"
@click="onDateRangeTypeChange(option.value)" @click="onDateRangeTypeChange(option.value)"
:disabled="isLoading" :disabled="isLoading"
> >
@ -205,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-7 md: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">
@ -212,7 +247,7 @@ onMounted(() => {
<Input <Input
type="date" type="date"
v-model="customStartDate" v-model="customStartDate"
class="h-8 text-xs" class="h-7 md:h-8 text-xs"
:disabled="isLoading" :disabled="isLoading"
/> />
</div> </div>
@ -221,13 +256,13 @@ onMounted(() => {
<Input <Input
type="date" type="date"
v-model="customEndDate" v-model="customEndDate"
class="h-8 text-xs" class="h-7 md:h-8 text-xs"
:disabled="isLoading" :disabled="isLoading"
/> />
</div> </div>
<Button <Button
size="sm" size="sm"
class="h-8 px-3 text-xs" class="h-7 md:h-8 px-3 text-xs"
@click="applyCustomDateRange" @click="applyCustomDateRange"
:disabled="isLoading || !customStartDate || !customEndDate" :disabled="isLoading || !customStartDate || !customEndDate"
> >
@ -281,7 +316,11 @@ onMounted(() => {
<div <div
v-for="transaction in transactionsToDisplay" v-for="transaction in transactionsToDisplay"
:key="transaction.id" :key="transaction.id"
class="border-b md:border md:rounded-lg p-4 hover:bg-muted/50 transition-colors" :class="[
'border-b md:border md:rounded-lg p-4 hover:bg-muted/50 transition-colors',
isIncome(transaction) && 'border-l-4 border-l-green-600',
isExpense(transaction) && 'border-l-4 border-l-red-600'
]"
> >
<!-- Transaction Header --> <!-- Transaction Header -->
<div class="flex items-start justify-between gap-3 mb-2"> <div class="flex items-start justify-between gap-3 mb-2">
@ -328,22 +367,21 @@ onMounted(() => {
<span class="font-medium">Ref:</span> {{ transaction.reference }} <span class="font-medium">Ref:</span> {{ transaction.reference }}
</div> </div>
<!-- Username (if available) -->
<div v-if="transaction.username" class="text-muted-foreground">
<span class="font-medium">User:</span> {{ transaction.username }}
</div>
<!-- Tags --> <!-- Tags -->
<div v-if="transaction.tags && transaction.tags.length > 0" class="flex flex-wrap gap-1 mt-2"> <div v-if="transaction.tags && transaction.tags.length > 0" class="flex flex-wrap gap-1 mt-2">
<Badge v-for="tag in transaction.tags" :key="tag" variant="secondary" class="text-xs"> <Badge
v-for="tag in transaction.tags"
:key="tag"
variant="secondary"
:class="[
'text-xs',
tag === 'income-entry' && 'bg-green-100 dark:bg-green-900/20 text-green-700 dark:text-green-300',
tag === 'expense-entry' && 'bg-red-100 dark:bg-red-900/20 text-red-700 dark:text-red-300'
]"
>
{{ tag }} {{ tag }}
</Badge> </Badge>
</div> </div>
<!-- Metadata Source -->
<div v-if="transaction.meta?.source" class="text-muted-foreground mt-1">
<span class="text-xs">Source: {{ transaction.meta.source }}</span>
</div>
</div> </div>
</div> </div>