Compare commits
4 commits
aef59cfc0c
...
9252c86f5c
| Author | SHA1 | Date | |
|---|---|---|---|
| 9252c86f5c | |||
| 22ad5fd974 | |||
| 8b91c7bd72 | |||
| 396b012707 |
1 changed files with 57 additions and 19 deletions
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue