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:
padreug 2025-11-14 16:39:32 +01:00
parent 9c4b14f382
commit 557d7ecacc

View file

@ -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>