Enhance WalletService and WalletPage for improved timestamp handling and layout

- Updated WalletService to robustly handle various timestamp formats, ensuring accurate transaction date representation.
- Enhanced WalletPage layout for better responsiveness, including mobile and desktop views, and improved transaction display with clearer formatting.
- Adjusted transaction item styling for consistency and better user experience across different screen sizes.

These changes improve the reliability and usability of transaction data presentation in the wallet module.
This commit is contained in:
padreug 2025-09-15 00:05:29 +02:00
parent 42b4af8fa5
commit d2a5d90427
2 changed files with 151 additions and 50 deletions

View file

@ -268,16 +268,35 @@ export default class WalletService extends BaseService {
const payments = await response.json() const payments = await response.json()
// Transform to our transaction format // Transform to our transaction format
this._transactions.value = payments.map((payment: any) => ({ this._transactions.value = payments.map((payment: any) => {
id: payment.payment_hash, let timestamp = new Date()
amount: Math.abs(payment.amount) / 1000,
description: payment.memo || payment.description || 'No description', if (payment.time) {
timestamp: payment.time ? new Date(payment.time * 1000) : new Date(), // Check if it's an ISO string or Unix timestamp
type: payment.amount > 0 ? 'received' : 'sent', if (typeof payment.time === 'string' && payment.time.includes('T')) {
status: payment.pending ? 'pending' : 'confirmed', // ISO string format (e.g., "2025-09-14T16:49:40.378877+00:00")
fee: payment.fee ? payment.fee / 1000 : undefined, timestamp = new Date(payment.time)
tag: payment.tag || (payment.extra && payment.extra.tag) || null } else if (typeof payment.time === 'number' || !isNaN(Number(payment.time))) {
})).sort((a: PaymentTransaction, b: PaymentTransaction) => // Unix timestamp (seconds) - multiply by 1000 for milliseconds
timestamp = new Date(Number(payment.time) * 1000)
} else {
// Try to parse as-is
timestamp = new Date(payment.time)
}
}
return {
id: payment.payment_hash,
amount: Math.abs(payment.amount) / 1000,
description: payment.memo || payment.description || 'No description',
timestamp: timestamp,
type: payment.amount > 0 ? 'received' : 'sent',
status: payment.pending ? 'pending' : 'confirmed',
fee: payment.fee ? payment.fee / 1000 : undefined,
tag: payment.tag || (payment.extra && payment.extra.tag) || null
}
}).sort((a: PaymentTransaction, b: PaymentTransaction) =>
b.timestamp.getTime() - a.timestamp.getTime() b.timestamp.getTime() - a.timestamp.getTime()
) )

View file

@ -178,52 +178,134 @@ onMounted(() => {
<div v-for="(txs, date) in groupedTransactions" :key="date" class="mb-6"> <div v-for="(txs, date) in groupedTransactions" :key="date" class="mb-6">
<div class="text-sm font-medium text-muted-foreground mb-3">{{ date }}</div> <div class="text-sm font-medium text-muted-foreground mb-3">{{ date }}</div>
<div class="space-y-2"> <div class="space-y-3">
<div <div
v-for="tx in txs" v-for="tx in txs"
:key="tx.id" :key="tx.id"
class="flex items-center justify-between p-4 rounded-lg border hover:bg-accent/50 transition-colors" class="p-3 rounded-lg border hover:bg-accent/50 transition-colors"
> >
<div class="flex items-center gap-4"> <!-- Mobile Layout: Stack vertically -->
<div <div class="block sm:hidden">
class="p-2 rounded-full bg-background border" <!-- Header Row: Icon, Amount, Type -->
:class="getTransactionColor(tx.type)" <div class="flex items-center justify-between mb-2">
> <div class="flex items-center gap-3">
<component <div
:is="getTransactionIcon(tx.type)" class="p-1.5 rounded-full bg-background border"
class="h-4 w-4" :class="getTransactionColor(tx.type)"
/> >
</div> <component
:is="getTransactionIcon(tx.type)"
<div> class="h-3.5 w-3.5"
<p class="font-medium">{{ tx.description }}</p> />
<div class="flex items-center gap-2 text-sm text-muted-foreground"> </div>
<span>{{ <span class="text-sm text-muted-foreground">{{
tx.timestamp instanceof Date && !isNaN(tx.timestamp.getTime()) (() => {
? format(tx.timestamp, 'HH:mm') try {
: '--:--' if (tx.timestamp instanceof Date && !isNaN(tx.timestamp.getTime())) {
return format(tx.timestamp, 'HH:mm')
} else if (tx.timestamp) {
// Try to parse as string or number
const date = new Date(tx.timestamp)
if (!isNaN(date.getTime())) {
return format(date, 'HH:mm')
}
}
return '--:--'
} catch (error) {
console.warn('Failed to format timestamp:', tx.timestamp, error)
return '--:--'
}
})()
}}</span> }}</span>
<Badge :variant="getStatusColor(tx.status)" class="text-xs"> </div>
{{ tx.status }}
</Badge> <div class="text-right">
<Badge v-if="tx.tag" variant="outline" class="text-xs"> <p
{{ tx.tag }} class="font-semibold text-base"
</Badge> :class="getTransactionColor(tx.type)"
>
{{ tx.type === 'received' ? '+' : '-' }}{{ tx.amount.toLocaleString() }}
</p>
<p class="text-xs text-muted-foreground">sats</p>
</div> </div>
</div> </div>
<!-- Description Row -->
<div class="mb-2">
<p class="font-medium text-sm leading-tight break-words pr-2">{{ tx.description }}</p>
</div>
<!-- Badges Row -->
<div class="flex items-center gap-2 flex-wrap">
<Badge :variant="getStatusColor(tx.status)" class="text-xs">
{{ tx.status }}
</Badge>
<Badge v-if="tx.tag" variant="outline" class="text-xs">
{{ tx.tag }}
</Badge>
<span v-if="tx.fee" class="text-xs text-muted-foreground">
Fee: {{ tx.fee }} sats
</span>
</div>
</div> </div>
<div class="text-right"> <!-- Desktop Layout: Original horizontal layout -->
<p <div class="hidden sm:flex items-center justify-between">
class="font-semibold" <div class="flex items-center gap-4">
:class="getTransactionColor(tx.type)" <div
> class="p-2 rounded-full bg-background border"
{{ tx.type === 'received' ? '+' : '-' }} :class="getTransactionColor(tx.type)"
{{ tx.amount.toLocaleString() }} sats >
</p> <component
<p v-if="tx.fee" class="text-xs text-muted-foreground"> :is="getTransactionIcon(tx.type)"
Fee: {{ tx.fee }} sats class="h-4 w-4"
</p> />
</div>
<div class="min-w-0 flex-1">
<p class="font-medium truncate">{{ tx.description }}</p>
<div class="flex items-center gap-2 text-sm text-muted-foreground">
<span>{{
(() => {
try {
if (tx.timestamp instanceof Date && !isNaN(tx.timestamp.getTime())) {
return format(tx.timestamp, 'HH:mm')
} else if (tx.timestamp) {
// Try to parse as string or number
const date = new Date(tx.timestamp)
if (!isNaN(date.getTime())) {
return format(date, 'HH:mm')
}
}
return '--:--'
} catch (error) {
console.warn('Failed to format timestamp:', tx.timestamp, error)
return '--:--'
}
})()
}}</span>
<Badge :variant="getStatusColor(tx.status)" class="text-xs">
{{ tx.status }}
</Badge>
<Badge v-if="tx.tag" variant="outline" class="text-xs">
{{ tx.tag }}
</Badge>
</div>
</div>
</div>
<div class="text-right ml-4">
<p
class="font-semibold"
:class="getTransactionColor(tx.type)"
>
{{ tx.type === 'received' ? '+' : '-' }}
{{ tx.amount.toLocaleString() }} sats
</p>
<p v-if="tx.fee" class="text-xs text-muted-foreground">
Fee: {{ tx.fee }} sats
</p>
</div>
</div> </div>
</div> </div>
</div> </div>