'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 { MAX_COIN_LIMIT } from '@/lib/constants' import { TransactionType } from '@/lib/types' import { d2s, t2d } from '@/lib/utils' import { useAtom } from 'jotai' import { History } from 'lucide-react' import { useTranslations } from 'next-intl' 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 t = useTranslations('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('') } } const getTransactionTypeLabel = (type: TransactionType) => { switch (type) { case 'HABIT_COMPLETION': return t('transactionTypeHabitCompletion'); case 'TASK_COMPLETION': return t('transactionTypeTaskCompletion'); case 'HABIT_UNDO': return t('transactionTypeHabitUndo'); case 'TASK_UNDO': return t('transactionTypeTaskUndo'); case 'WISH_REDEMPTION': return t('transactionTypeWishRedemption'); case 'MANUAL_ADJUSTMENT': return t('transactionTypeManualAdjustment'); } } return (

{t('title')}

{currentUser?.isAdmin && ( )}
๐Ÿ’ฐ
{t('currentBalanceLabel')}
{t('coinsSuffix')}
{ const rawValue = e.target.value; if (rawValue === '' || rawValue === '-') { setAmount(rawValue); return; } let numericValue = Number(rawValue); // Changed const to let if (isNaN(numericValue)) return; // Or handle error if (Math.abs(numericValue) > MAX_COIN_LIMIT) { numericValue = numericValue < 0 ? -MAX_COIN_LIMIT : MAX_COIN_LIMIT; } setAmount(numericValue.toString()); }} min={-MAX_COIN_LIMIT} max={MAX_COIN_LIMIT} className="text-center text-xl font-medium h-12" />
๐Ÿช™
{t('statisticsTitle')}
{/* Top Row - Totals */}
{t('totalEarnedLabel')}
๐Ÿช™
{t('totalSpentLabel')}
๐Ÿ’ธ
{t('totalTransactionsLabel')}
{transactions.length} ๐Ÿ“ˆ
{/* Bottom Row - Today */}
{t('todaysEarnedLabel')}
๐Ÿช™
{t('todaysSpentLabel')}
๐Ÿ’ธ
{t('todaysTransactionsLabel')}
{transactionsToday} ๐Ÿ“Š
{t('transactionHistoryTitle')}
{t('showLabel')} {t('entriesSuffix')}
{t('showingEntries', { from: Math.min((currentPage - 1) * pageSize + 1, transactions.length), to: Math.min(currentPage * pageSize, transactions.length), total: transactions.length })}
{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}

)} {getTransactionTypeLabel(transaction.type as TransactionType)} {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}
) })}
{t('pageLabel')} {currentPage} {t('ofLabel')} {Math.ceil(transactions.length / pageSize)}
)}
) }