Optimize TransactionsPage for mobile view
Dramatically reduced wasted space and improved mobile UX by: - **Compact header**: Moved refresh button inline with title, similar to NostrFeed - **Compact controls**: All day filter buttons now on one row with Calendar icon - **Removed nested cards**: Eliminated Card wrapper around transactions list - **Full-width layout**: Transactions now use full screen width on mobile (border-b) and rounded cards on desktop (md:border md:rounded-lg) - **Consistent padding**: Uses px-0 on mobile, px-4 on desktop, matching NostrFeed patterns - **Reduced vertical space**: Compacted header section to about half the original height - **Cleaner imports**: Removed unused Card, CardContent, CardHeader, CardTitle, CardDescription, and Separator components Layout now follows NostrFeed's mobile-optimized patterns with max-w-3xl container and responsive spacing. Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9c4b14f382
commit
557d7ecacc
1 changed files with 142 additions and 145 deletions
|
|
@ -8,10 +8,8 @@ import type { Transaction } from '../types'
|
||||||
import type { FuzzySearchOptions } from '@/composables/useFuzzySearch'
|
import type { FuzzySearchOptions } from '@/composables/useFuzzySearch'
|
||||||
import FuzzySearch from '@/components/ui/fuzzy-search/FuzzySearch.vue'
|
import FuzzySearch from '@/components/ui/fuzzy-search/FuzzySearch.vue'
|
||||||
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Separator } from '@/components/ui/separator'
|
|
||||||
import {
|
import {
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Clock,
|
Clock,
|
||||||
|
|
@ -177,39 +175,36 @@ onMounted(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto p-4 max-w-4xl">
|
<div class="flex flex-col">
|
||||||
<!-- Header -->
|
<!-- Compact Header -->
|
||||||
<div class="mb-6">
|
<div class="flex flex-col gap-3 p-4 md:p-6 border-b md:bg-card/50 md:backdrop-blur-sm">
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold mb-2">My Transactions</h1>
|
<div class="w-full max-w-3xl mx-auto">
|
||||||
<p class="text-muted-foreground">View your recent transaction history</p>
|
<div class="flex items-center justify-between mb-3">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-lg md:text-xl font-bold">My Transactions</h1>
|
||||||
|
<p class="text-xs md:text-sm text-muted-foreground">View your recent transaction history</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
@click="loadTransactions"
|
||||||
|
:disabled="isLoading"
|
||||||
|
class="gap-2 md:h-10 md:px-4 hover:bg-accent transition-colors"
|
||||||
|
>
|
||||||
|
<RefreshCw :class="{ 'animate-spin': isLoading }" class="h-4 w-4" />
|
||||||
|
<span class="hidden md:inline">Refresh</span>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search Bar -->
|
<!-- Compact Controls Row -->
|
||||||
<div class="mb-4">
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
<FuzzySearch
|
<Calendar class="h-4 w-4 text-muted-foreground" />
|
||||||
:data="transactions"
|
|
||||||
:options="searchOptions"
|
|
||||||
placeholder="Search transactions by description, payee, reference..."
|
|
||||||
@results="handleSearchResults"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Controls -->
|
|
||||||
<Card class="mb-4">
|
|
||||||
<CardContent class="pt-6">
|
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|
||||||
<!-- Day Filter -->
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="text-sm text-muted-foreground flex items-center gap-2">
|
|
||||||
<Calendar class="h-4 w-4" />
|
|
||||||
<span>Show transactions from:</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<Button
|
<Button
|
||||||
v-for="option in dayOptions"
|
v-for="option in dayOptions"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:variant="selectedDays === option.value ? 'default' : 'outline'"
|
:variant="selectedDays === option.value ? 'default' : 'outline'"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
class="h-8 px-3 text-xs"
|
||||||
@click="changeDayFilter(option.value)"
|
@click="changeDayFilter(option.value)"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
>
|
>
|
||||||
|
|
@ -217,51 +212,52 @@ onMounted(() => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Refresh Button -->
|
|
||||||
<Button variant="outline" size="sm" @click="loadTransactions" :disabled="isLoading">
|
|
||||||
<RefreshCw class="h-4 w-4" :class="{ 'animate-spin': isLoading }" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Transactions List -->
|
<!-- Content Container -->
|
||||||
<Card>
|
<div class="w-full max-w-3xl mx-auto px-0 md:px-4">
|
||||||
<CardHeader>
|
<!-- Search Bar -->
|
||||||
<CardTitle>Recent Transactions</CardTitle>
|
<div class="px-4 md:px-0 py-3">
|
||||||
<CardDescription>
|
<FuzzySearch
|
||||||
|
:data="transactions"
|
||||||
|
:options="searchOptions"
|
||||||
|
placeholder="Search transactions..."
|
||||||
|
@results="handleSearchResults"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results Count -->
|
||||||
|
<div class="px-4 md:px-0 py-2 text-xs md:text-sm text-muted-foreground">
|
||||||
<span v-if="searchResults.length > 0">
|
<span v-if="searchResults.length > 0">
|
||||||
Found {{ transactionsToDisplay.length }} matching transaction{{ transactionsToDisplay.length === 1 ? '' : 's' }}
|
Found {{ transactionsToDisplay.length }} matching transaction{{ transactionsToDisplay.length === 1 ? '' : 's' }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
Showing {{ pagination.offset + 1 }} -
|
Showing {{ pagination.offset + 1 }} - {{ Math.min(pagination.offset + pagination.limit, pagination.total) }} of {{ pagination.total }} transactions
|
||||||
{{ Math.min(pagination.offset + pagination.limit, pagination.total) }} of
|
|
||||||
{{ pagination.total }} transactions
|
|
||||||
</span>
|
</span>
|
||||||
</CardDescription>
|
</div>
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div v-if="isLoading" class="text-center py-12">
|
<div v-if="isLoading" class="flex items-center justify-center py-12">
|
||||||
<RefreshCw class="h-8 w-8 animate-spin mx-auto mb-4 text-muted-foreground" />
|
<div class="flex items-center gap-2">
|
||||||
<p class="text-muted-foreground">Loading transactions...</p>
|
<RefreshCw class="h-4 w-4 animate-spin" />
|
||||||
|
<span class="text-muted-foreground">Loading transactions...</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Empty State -->
|
<!-- Empty State -->
|
||||||
<div v-else-if="transactionsToDisplay.length === 0" class="text-center py-12">
|
<div v-else-if="transactionsToDisplay.length === 0" class="text-center py-12 px-4">
|
||||||
<p class="text-muted-foreground">No transactions found</p>
|
<p class="text-muted-foreground">No transactions found</p>
|
||||||
<p class="text-sm text-muted-foreground mt-2">
|
<p class="text-sm text-muted-foreground mt-2">
|
||||||
{{ searchResults.length > 0 ? 'Try a different search term' : 'Try selecting a different time period' }}
|
{{ searchResults.length > 0 ? 'Try a different search term' : 'Try selecting a different time period' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Transaction Items (Mobile-Optimized) -->
|
<!-- Transaction Items (Full-width on mobile, no nested cards) -->
|
||||||
<div v-else class="space-y-3">
|
<div v-else class="md:space-y-3 md:py-4">
|
||||||
<div
|
<div
|
||||||
v-for="(transaction, index) in transactionsToDisplay"
|
v-for="transaction in transactionsToDisplay"
|
||||||
:key="transaction.id"
|
:key="transaction.id"
|
||||||
class="border 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"
|
||||||
>
|
>
|
||||||
<!-- 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">
|
||||||
|
|
@ -296,7 +292,7 @@ onMounted(() => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Transaction Details (Collapsible on mobile) -->
|
<!-- Transaction Details -->
|
||||||
<div class="space-y-1 text-xs sm:text-sm">
|
<div class="space-y-1 text-xs sm:text-sm">
|
||||||
<!-- Payee -->
|
<!-- Payee -->
|
||||||
<div v-if="transaction.payee" class="text-muted-foreground">
|
<div v-if="transaction.payee" class="text-muted-foreground">
|
||||||
|
|
@ -325,16 +321,12 @@ onMounted(() => {
|
||||||
<span class="text-xs">Source: {{ transaction.meta.source }}</span>
|
<span class="text-xs">Source: {{ transaction.meta.source }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Separator between items (except last) -->
|
|
||||||
<Separator v-if="index < transactionsToDisplay.length - 1" class="mt-3" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pagination (hide when searching) -->
|
<!-- Pagination (hide when searching) -->
|
||||||
<div
|
<div
|
||||||
v-if="!isLoading && transactions.length > 0 && searchResults.length === 0 && (pagination.has_next || pagination.has_prev)"
|
v-if="!isLoading && transactions.length > 0 && searchResults.length === 0 && (pagination.has_next || pagination.has_prev)"
|
||||||
class="flex items-center justify-between mt-6 pt-4 border-t"
|
class="flex items-center justify-between px-4 md:px-0 py-6 border-t"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
@ -360,7 +352,12 @@ onMounted(() => {
|
||||||
<ChevronRight class="h-4 w-4 ml-1" />
|
<ChevronRight class="h-4 w-4 ml-1" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
<!-- End of list indicator -->
|
||||||
|
<div v-if="transactionsToDisplay.length > 0" class="text-center py-6 text-md text-muted-foreground">
|
||||||
|
<p>🐢</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue