mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-01-21 06:34:30 +01:00
show total coins in header, add pagination on coin transaction (#24)
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Version 0.1.12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- show total coins in header
|
||||||
|
- pagination for coin transactions history
|
||||||
|
|
||||||
## Version 0.1.11
|
## Version 0.1.11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export default function CoinsManager() {
|
|||||||
const [settings] = useAtom(settingsAtom)
|
const [settings] = useAtom(settingsAtom)
|
||||||
const DEFAULT_AMOUNT = '0'
|
const DEFAULT_AMOUNT = '0'
|
||||||
const [amount, setAmount] = useState(DEFAULT_AMOUNT)
|
const [amount, setAmount] = useState(DEFAULT_AMOUNT)
|
||||||
|
const [pageSize, setPageSize] = useState(50)
|
||||||
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
|
|
||||||
const handleAddRemoveCoins = async () => {
|
const handleAddRemoveCoins = async () => {
|
||||||
const numAmount = Number(amount)
|
const numAmount = Number(amount)
|
||||||
@@ -143,15 +145,32 @@ export default function CoinsManager() {
|
|||||||
|
|
||||||
<Card className="md:col-span-2">
|
<Card className="md:col-span-2">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex justify-between items-center">
|
<CardTitle>Transaction History</CardTitle>
|
||||||
<span>Transaction History</span>
|
|
||||||
<span className="text-sm font-normal text-muted-foreground">
|
|
||||||
Total: {transactions.length} transactions
|
|
||||||
</span>
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm text-muted-foreground">Show:</span>
|
||||||
|
<select
|
||||||
|
className="border rounded p-1"
|
||||||
|
value={pageSize}
|
||||||
|
onChange={(e) => {
|
||||||
|
setPageSize(Number(e.target.value))
|
||||||
|
setCurrentPage(1) // Reset to first page when changing page size
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value={50}>50</option>
|
||||||
|
<option value={100}>100</option>
|
||||||
|
<option value={500}>500</option>
|
||||||
|
</select>
|
||||||
|
<span className="text-sm text-muted-foreground">entries</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Showing {Math.min((currentPage - 1) * pageSize + 1, transactions.length)} to {Math.min(currentPage * pageSize, transactions.length)} of {transactions.length} entries
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{transactions.length === 0 ? (
|
{transactions.length === 0 ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
icon={History}
|
icon={History}
|
||||||
@@ -159,61 +178,108 @@ export default function CoinsManager() {
|
|||||||
description="Your transaction history will appear here once you start earning or spending coins"
|
description="Your transaction history will appear here once you start earning or spending coins"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
transactions.map((transaction) => {
|
<>
|
||||||
const getBadgeStyles = () => {
|
{transactions
|
||||||
switch (transaction.type) {
|
.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
||||||
case 'HABIT_COMPLETION':
|
.map((transaction) => {
|
||||||
return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100'
|
const getBadgeStyles = () => {
|
||||||
case 'HABIT_UNDO':
|
switch (transaction.type) {
|
||||||
return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100'
|
case 'HABIT_COMPLETION':
|
||||||
case 'WISH_REDEMPTION':
|
return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100'
|
||||||
return 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100'
|
case 'HABIT_UNDO':
|
||||||
case 'MANUAL_ADJUSTMENT':
|
return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100'
|
||||||
return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-100'
|
case 'WISH_REDEMPTION':
|
||||||
default:
|
return 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100'
|
||||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-100'
|
case 'MANUAL_ADJUSTMENT':
|
||||||
}
|
return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-100'
|
||||||
}
|
default:
|
||||||
|
return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-100'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={transaction.id}
|
key={transaction.id}
|
||||||
className="flex justify-between items-center p-3 border rounded hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
className="flex justify-between items-center p-3 border rounded hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||||
>
|
>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{transaction.relatedItemId ? (
|
{transaction.relatedItemId ? (
|
||||||
<Link
|
<Link
|
||||||
href={`${transaction.type === 'WISH_REDEMPTION' ? '/wishlist' : '/habits'}?highlight=${transaction.relatedItemId}`}
|
href={`${transaction.type === 'WISH_REDEMPTION' ? '/wishlist' : '/habits'}?highlight=${transaction.relatedItemId}`}
|
||||||
className="font-medium hover:underline"
|
className="font-medium hover:underline"
|
||||||
scroll={true}
|
scroll={true}
|
||||||
>
|
>
|
||||||
{transaction.description}
|
{transaction.description}
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<p className="font-medium">{transaction.description}</p>
|
<p className="font-medium">{transaction.description}</p>
|
||||||
)}
|
)}
|
||||||
|
<span
|
||||||
|
className={`text-xs px-2 py-1 rounded-full ${getBadgeStyles()}`}
|
||||||
|
>
|
||||||
|
{transaction.type.split('_').join(' ')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{d2s({ dateTime: t2d({ timestamp: transaction.timestamp, timezone: settings.system.timezone }), timezone: settings.system.timezone })}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<span
|
<span
|
||||||
className={`text-xs px-2 py-1 rounded-full ${getBadgeStyles()}`}
|
className={`font-mono ${transaction.amount >= 0
|
||||||
|
? 'text-green-600 dark:text-green-400'
|
||||||
|
: 'text-red-600 dark:text-red-400'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{transaction.type.split('_').join(' ')}
|
{transaction.amount >= 0 ? '+' : ''}{transaction.amount}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-500">
|
)
|
||||||
{d2s({ dateTime: t2d({ timestamp: transaction.timestamp, timezone: settings.system.timezone }), timezone: settings.system.timezone })}
|
})}
|
||||||
</p>
|
|
||||||
</div>
|
<div className="flex justify-center items-center gap-4 mt-6">
|
||||||
<span
|
<div className="flex items-center gap-2">
|
||||||
className={`font-mono ${transaction.amount >= 0
|
<Button
|
||||||
? 'text-green-600 dark:text-green-400'
|
variant="outline"
|
||||||
: 'text-red-600 dark:text-red-400'
|
size="sm"
|
||||||
}`}
|
onClick={() => setCurrentPage(1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
>
|
>
|
||||||
{transaction.amount >= 0 ? '+' : ''}{transaction.amount}
|
«
|
||||||
</span>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
‹
|
||||||
|
</Button>
|
||||||
|
<div className="flex items-center gap-1 px-4 py-2 rounded-md bg-muted">
|
||||||
|
<span className="text-sm font-medium">Page</span>
|
||||||
|
<span className="text-sm font-bold">{currentPage}</span>
|
||||||
|
<span className="text-sm font-medium">of</span>
|
||||||
|
<span className="text-sm font-bold">{Math.ceil(transactions.length / pageSize)}</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setCurrentPage(prev => Math.min(Math.ceil(transactions.length / pageSize), prev + 1))}
|
||||||
|
disabled={currentPage >= Math.ceil(transactions.length / pageSize)}
|
||||||
|
>
|
||||||
|
›
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setCurrentPage(Math.ceil(transactions.length / pageSize))}
|
||||||
|
disabled={currentPage >= Math.ceil(transactions.length / pageSize)}
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
})
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useAtom } from 'jotai'
|
import { useAtom } from 'jotai'
|
||||||
import { settingsAtom } from '@/lib/atoms'
|
import { settingsAtom, coinsAtom } from '@/lib/atoms'
|
||||||
import { Bell, Menu, Settings, User, Info } from 'lucide-react'
|
import { Bell, Menu, Settings, User, Info, Coins } from 'lucide-react'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Logo } from '@/components/Logo'
|
import { Logo } from '@/components/Logo'
|
||||||
import {
|
import {
|
||||||
@@ -23,6 +23,7 @@ interface HeaderProps {
|
|||||||
export default function Header({ className }: HeaderProps) {
|
export default function Header({ className }: HeaderProps) {
|
||||||
const [showAbout, setShowAbout] = useState(false)
|
const [showAbout, setShowAbout] = useState(false)
|
||||||
const [settings] = useAtom(settingsAtom)
|
const [settings] = useAtom(settingsAtom)
|
||||||
|
const [coins] = useAtom(coinsAtom)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header className={`border-b bg-white dark:bg-gray-800 shadow-sm ${className || ''}`}>
|
<header className={`border-b bg-white dark:bg-gray-800 shadow-sm ${className || ''}`}>
|
||||||
@@ -32,6 +33,12 @@ export default function Header({ className }: HeaderProps) {
|
|||||||
<Logo />
|
<Logo />
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<Link href="/coins" className="flex items-center gap-1 px-2 py-1 bg-amber-100 hover:bg-amber-200 dark:bg-amber-900 dark:hover:bg-amber-800 rounded-full transition-colors">
|
||||||
|
<Coins className="text-amber-500 dark:text-amber-300" />
|
||||||
|
<span className="text-amber-600 dark:text-amber-400 font-medium">
|
||||||
|
{coins.balance}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
<Button variant="ghost" size="icon" aria-label="Notifications">
|
<Button variant="ghost" size="icon" aria-label="Notifications">
|
||||||
<Bell className="h-5 w-5" />
|
<Bell className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "habittrove",
|
"name": "habittrove",
|
||||||
"version": "0.1.11",
|
"version": "0.1.12",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
|
|||||||
Reference in New Issue
Block a user