'use client' import { FormattedNumber } from '@/components/FormattedNumber' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { useCoins } from '@/hooks/useCoins' import { settingsAtom, usersAtom } from '@/lib/atoms' import { useHelpers } from '@/lib/client-helpers' import { d2s, t2d } from '@/lib/utils' import { useAtom } from 'jotai' import { History } from 'lucide-react' import Link from 'next/link' import { useSearchParams } from 'next/navigation'; // Import useSearchParams import { useEffect, useRef, useState } from 'react'; // Import useEffect, useRef import EmptyState from './EmptyState' import { TransactionNoteEditor } from './TransactionNoteEditor' export default function CoinsManager() { const { currentUser } = useHelpers() const [selectedUser, setSelectedUser] = useState() const { add, remove, updateNote, balance, transactions, coinsEarnedToday, totalEarned, totalSpent, coinsSpentToday, transactionsToday } = useCoins({selectedUser}) const [settings] = useAtom(settingsAtom) const [usersData] = useAtom(usersAtom) const DEFAULT_AMOUNT = '0' const [amount, setAmount] = useState(DEFAULT_AMOUNT) const [pageSize, setPageSize] = useState(50) const [currentPage, setCurrentPage] = useState(1) const [note, setNote] = useState('') const searchParams = useSearchParams() const highlightId = searchParams.get('highlight') const userIdFromQuery = searchParams.get('user') // Get user ID from query const transactionRefs = useRef>({}); const PAGE_ENTRY_COUNTS = [10, 50, 100, 500]; // Effect to set selected user from query param if admin useEffect(() => { if (currentUser?.isAdmin && userIdFromQuery && userIdFromQuery !== selectedUser) { // Check if the user ID from query exists in usersData if (usersData.users.some(u => u.id === userIdFromQuery)) { setSelectedUser(userIdFromQuery); } } // Only run when userIdFromQuery or currentUser changes, avoid re-running on selectedUser change within this effect }, [userIdFromQuery, currentUser, usersData.users, selectedUser]); // Effect to scroll to highlighted transaction useEffect(() => { if (highlightId && transactionRefs.current[highlightId]) { transactionRefs.current[highlightId]?.scrollIntoView({ behavior: 'smooth', block: 'center', }); } }, [highlightId, transactions]); // Re-run if highlightId or transactions change const handleSaveNote = async (transactionId: string, note: string) => { await updateNote(transactionId, note) } const handleDeleteNote = async (transactionId: string) => { await updateNote(transactionId, '') } const handleAddRemoveCoins = async () => { const numAmount = Number(amount) if (numAmount > 0) { await add(numAmount, "Manual addition", note) setAmount(DEFAULT_AMOUNT) setNote('') } else if (numAmount < 0) { await remove(Math.abs(numAmount), "Manual removal", note) setAmount(DEFAULT_AMOUNT) setNote('') } } return (

Coins Management

{currentUser?.isAdmin && ( )}
๐Ÿ’ฐ
Current Balance
coins
setAmount(e.target.value)} className="text-center text-xl font-medium h-12" />
๐Ÿช™
Statistics
{/* Top Row - Totals */}
Total Earned
๐Ÿช™
Total Spent
๐Ÿ’ธ
Total Transactions
{transactions.length} ๐Ÿ“ˆ
{/* Bottom Row - Today */}
Today's Earned
๐Ÿช™
Today's Spent
๐Ÿ’ธ
Today's Transactions
{transactionsToday} ๐Ÿ“Š
Transaction History
Show: entries
Showing {Math.min((currentPage - 1) * pageSize + 1, transactions.length)} to {Math.min(currentPage * pageSize, transactions.length)} of {transactions.length} entries
{transactions.length === 0 ? ( ) : ( <> {transactions .slice((currentPage - 1) * pageSize, currentPage * pageSize) .map((transaction) => { const getBadgeStyles = () => { switch (transaction.type) { case 'HABIT_COMPLETION': return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100' case 'HABIT_UNDO': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100' case 'WISH_REDEMPTION': return 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-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' } } const isHighlighted = transaction.id === highlightId; const transactionUser = usersData.users.find(u => u.id === transaction.userId); return (
{ transactionRefs.current[transaction.id] = el; }} // Assign ref correctly className={`flex justify-between items-center p-3 border rounded hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors ${ isHighlighted ? 'ring-2 ring-blue-500 bg-blue-50 dark:bg-blue-900/30' : '' // Apply highlight styles }`} >
{/* Added flex-grow and margin */}
{/* Added flex-wrap */} {transaction.relatedItemId ? ( {transaction.description} ) : (

{transaction.description}

)} {transaction.type.split('_').join(' ')} {transaction.userId && currentUser?.isAdmin && ( {transactionUser?.username?.[0] || '?'} )}

{d2s({ dateTime: t2d({ timestamp: transaction.timestamp, timezone: settings.system.timezone }), timezone: settings.system.timezone })}

{/* Ensure amount stays on the right */} = 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }`} > {transaction.amount >= 0 ? '+' : ''}{transaction.amount}
) })}
Page {currentPage} of {Math.ceil(transactions.length / pageSize)}
)}
) }