diff --git a/components/CoinsManager.tsx b/components/CoinsManager.tsx index f344465..c67685a 100644 --- a/components/CoinsManager.tsx +++ b/components/CoinsManager.tsx @@ -10,7 +10,7 @@ import { useCoins } from '@/hooks/useCoins' import { currentUserAtom, settingsAtom, usersAtom } from '@/lib/atoms' import { MAX_COIN_LIMIT } from '@/lib/constants' import { TransactionType } from '@/lib/types' -import { d2s, t2d } from '@/lib/utils' +import { calculateTransactionsToday, d2s, t2d } from '@/lib/utils' import { useAtom } from 'jotai' import { History } from 'lucide-react' import { useTranslations } from 'next-intl' @@ -33,8 +33,7 @@ export default function CoinsManager() { coinsEarnedToday, totalEarned, totalSpent, - coinsSpentToday, - transactionsToday + coinsSpentToday } = useCoins({ selectedUser }) const [settings] = useAtom(settingsAtom) const [usersData] = useAtom(usersAtom) @@ -252,7 +251,7 @@ export default function CoinsManager() {
{t('todaysTransactionsLabel')}
- {transactionsToday} 📊 + {calculateTransactionsToday(transactions, settings.system.timezone)} 📊
diff --git a/hooks/useCoins.tsx b/hooks/useCoins.tsx index 019004a..5a3431f 100644 --- a/hooks/useCoins.tsx +++ b/hooks/useCoins.tsx @@ -2,15 +2,14 @@ import { addCoins, removeCoins, saveCoinsData } from '@/app/actions/data'; import { toast } from '@/hooks/use-toast'; import { coinsAtom, - coinsBalanceAtom, coinsEarnedTodayAtom, coinsSpentTodayAtom, currentUserAtom, + currentUserIdAtom, settingsAtom, totalEarnedAtom, totalSpentAtom, - transactionsTodayAtom, - usersAtom, + usersAtom } from '@/lib/atoms'; import { MAX_COIN_LIMIT } from '@/lib/constants'; import { CoinsData } from '@/lib/types'; @@ -24,27 +23,26 @@ export function useCoins(options?: { selectedUser?: string }) { const tCommon = useTranslations('Common'); const [coins, setCoins] = useAtom(coinsAtom) const [settings] = useAtom(settingsAtom) - const [users] = useAtom(usersAtom) + const [{users}] = useAtom(usersAtom) const [currentUser] = useAtom(currentUserAtom) - const [allCoinsData] = useAtom(coinsAtom) // All coin transactions - const [loggedInUserBalance] = useAtom(coinsBalanceAtom) // Balance of the *currently logged-in* user + const [coinsData] = useAtom(coinsAtom) // All coin transactions + const [loggedInUserId] = useAtom(currentUserIdAtom); + const loggedInUserBalance = loggedInUserId ? coins.transactions.filter(transaction => transaction.userId === loggedInUserId).reduce((sum, transaction) => sum + transaction.amount, 0) : 0; const [atomCoinsEarnedToday] = useAtom(coinsEarnedTodayAtom); const [atomTotalEarned] = useAtom(totalEarnedAtom) const [atomTotalSpent] = useAtom(totalSpentAtom) const [atomCoinsSpentToday] = useAtom(coinsSpentTodayAtom); - const [atomTransactionsToday] = useAtom(transactionsTodayAtom); - const targetUser = options?.selectedUser ? users.users.find(u => u.id === options.selectedUser) : currentUser + const targetUser = options?.selectedUser ? users.find(u => u.id === options.selectedUser) : currentUser const transactions = useMemo(() => { - return allCoinsData.transactions.filter(t => t.userId === targetUser?.id); - }, [allCoinsData, targetUser?.id]); + return coinsData.transactions.filter(t => t.userId === targetUser?.id); + }, [coinsData, targetUser?.id]); const timezone = settings.system.timezone; const [coinsEarnedToday, setCoinsEarnedToday] = useState(0); const [totalEarned, setTotalEarned] = useState(0); const [totalSpent, setTotalSpent] = useState(0); const [coinsSpentToday, setCoinsSpentToday] = useState(0); - const [transactionsToday, setTransactionsToday] = useState(0); const [balance, setBalance] = useState(0); useEffect(() => { @@ -55,7 +53,6 @@ export function useCoins(options?: { selectedUser?: string }) { setTotalEarned(atomTotalEarned); setTotalSpent(atomTotalSpent); setCoinsSpentToday(atomCoinsSpentToday); - setTransactionsToday(atomTransactionsToday); setBalance(loggedInUserBalance); } else if (targetUser?.id) { // If an admin is viewing another user, calculate their metrics manually @@ -71,8 +68,6 @@ export function useCoins(options?: { selectedUser?: string }) { const spentToday = calculateCoinsSpentToday(transactions, timezone); setCoinsSpentToday(roundToInteger(spentToday)); - setTransactionsToday(calculateTransactionsToday(transactions, timezone)); // This is a count - const calculatedBalance = transactions.reduce((acc, t) => acc + t.amount, 0); setBalance(roundToInteger(calculatedBalance)); } @@ -85,8 +80,7 @@ export function useCoins(options?: { selectedUser?: string }) { atomCoinsEarnedToday, atomTotalEarned, atomTotalSpent, - atomCoinsSpentToday, - atomTransactionsToday, + atomCoinsSpentToday ]); const add = async (amount: number, description: string, note?: string) => { @@ -187,7 +181,6 @@ export function useCoins(options?: { selectedUser?: string }) { coinsEarnedToday, totalEarned, totalSpent, - coinsSpentToday, - transactionsToday + coinsSpentToday } } diff --git a/lib/atoms.ts b/lib/atoms.ts index e8e4730..bdeedc3 100644 --- a/lib/atoms.ts +++ b/lib/atoms.ts @@ -3,10 +3,7 @@ import { calculateCoinsSpentToday, calculateTotalEarned, calculateTotalSpent, - calculateTransactionsToday, generateCryptoHash, - getCompletionsForToday, - getHabitFreq, isHabitDue, prepareDataForHashing, roundToInteger, @@ -16,9 +13,9 @@ import { atom } from "jotai"; import { atomFamily, atomWithStorage } from "jotai/utils"; import { DateTime } from "luxon"; import { + BrowserSettings, CoinsData, CompletionCache, - Freq, getDefaultCoinsData, getDefaultHabitsData, getDefaultServerSettings, @@ -27,6 +24,7 @@ import { getDefaultWishlistData, Habit, HabitsData, + PomodoroAtom, ServerSettings, Settings, UserData, @@ -34,12 +32,6 @@ import { WishlistData } from "./types"; -export interface BrowserSettings { - expandedHabits: boolean - expandedTasks: boolean - expandedWishlist: boolean -} - export const browserSettingsAtom = atomWithStorage('browserSettings', { expandedHabits: false, expandedTasks: false, @@ -47,11 +39,21 @@ export const browserSettingsAtom = atomWithStorage('browserSettings', { } as BrowserSettings) export const usersAtom = atom(getDefaultUsersData()) +export const currentUserIdAtom = atom(undefined); export const settingsAtom = atom(getDefaultSettings()); export const habitsAtom = atom(getDefaultHabitsData()); export const coinsAtom = atom(getDefaultCoinsData()); export const wishlistAtom = atom(getDefaultWishlistData()); export const serverSettingsAtom = atom(getDefaultServerSettings()); +export const userSelectAtom = atom(false) +export const aboutOpenAtom = atom(false) + +export const pomodoroAtom = atom({ + show: false, + selectedHabitId: null, + autoStart: true, + minimized: false, +}) // Derived atom for coins earned today export const coinsEarnedTodayAtom = atom((get) => { @@ -83,54 +85,12 @@ export const coinsSpentTodayAtom = atom((get) => { return roundToInteger(value); }); -// Derived atom for transactions today -export const transactionsTodayAtom = atom((get) => { - const coins = get(coinsAtom); - const settings = get(settingsAtom); - return calculateTransactionsToday(coins.transactions, settings.system.timezone); -}); - -// Atom to store the current logged-in user's ID. -// This should be set by your application when the user session is available. -export const currentUserIdAtom = atom(undefined); - export const currentUserAtom = atom((get) => { const currentUserId = get(currentUserIdAtom); const users = get(usersAtom); return users.users.find(user => user.id === currentUserId); }) -// Derived atom for current balance for the logged-in user -export const coinsBalanceAtom = atom((get) => { - const loggedInUserId = get(currentUserIdAtom); - if (!loggedInUserId) { - return 0; // No user logged in or ID not set, so balance is 0 - } - const coins = get(coinsAtom); - const balance = coins.transactions - .filter(transaction => transaction.userId === loggedInUserId) - .reduce((sum, transaction) => sum + transaction.amount, 0); - return roundToInteger(balance); -}); - -/* transient atoms */ -export interface PomodoroAtom { - show: boolean - selectedHabitId: string | null - autoStart: boolean - minimized: boolean -} - -export const pomodoroAtom = atom({ - show: false, - selectedHabitId: null, - autoStart: true, - minimized: false, -}) - -export const userSelectAtom = atom(false) -export const aboutOpenAtom = atom(false) - /** * Asynchronous atom that calculates a freshness token (hash) based on the current client-side data. * This token can be compared with a server-generated token to detect data discrepancies. @@ -147,34 +107,26 @@ export const clientFreshnessTokenAtom = atom(async (get) => { return hash; }); -// Derived atom for completion cache -export const completionCacheAtom = atom((get) => { +// Derived atom for completed habits by date, using the cache +export const completedHabitsMapAtom = atom((get) => { const habits = get(habitsAtom).habits; + const completionCache: CompletionCache = {}; + const map = new Map(); const timezone = get(settingsAtom).system.timezone; - const cache: CompletionCache = {}; habits.forEach(habit => { habit.completions.forEach(utcTimestamp => { const localDate = t2d({ timestamp: utcTimestamp, timezone }) .toFormat('yyyy-MM-dd'); - if (!cache[localDate]) { - cache[localDate] = {}; + if (!completionCache[localDate]) { + completionCache[localDate] = {}; } - cache[localDate][habit.id] = (cache[localDate][habit.id] || 0) + 1; + completionCache[localDate][habit.id] = (completionCache[localDate][habit.id] || 0) + 1; }); }); - return cache; -}); - -// Derived atom for completed habits by date, using the cache -export const completedHabitsMapAtom = atom((get) => { - const habits = get(habitsAtom).habits; - const completionCache = get(completionCacheAtom); - const map = new Map(); - // For each date in the cache Object.entries(completionCache).forEach(([dateKey, habitCompletions]) => { const completedHabits = habits.filter(habit => { diff --git a/lib/types.ts b/lib/types.ts index 25d5b33..f4eecf0 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -210,3 +210,16 @@ export interface ParsedFrequencyResult { message: string | null result: ParsedResultType } + +export interface PomodoroAtom { + show: boolean + selectedHabitId: string | null + autoStart: boolean + minimized: boolean +} + +export interface BrowserSettings { + expandedHabits: boolean + expandedTasks: boolean + expandedWishlist: boolean +} \ No newline at end of file diff --git a/lib/utils.ts b/lib/utils.ts index 85e238a..28939a5 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,12 +1,11 @@ import { toast } from "@/hooks/use-toast" -import { CoinsData, CoinTransaction, Freq, Habit, HabitsData, ParsedFrequencyResult, ParsedResultType, SafeUser, Settings, User, UserData, WishlistData } from '@/lib/types' +import { CoinsData, CoinTransaction, Freq, Habit, HabitsData, ParsedFrequencyResult, ParsedResultType, PomodoroAtom, SafeUser, Settings, User, UserData, WishlistData } from '@/lib/types' import * as chrono from 'chrono-node' import { clsx, type ClassValue } from "clsx" import { DateTime, DateTimeFormatOptions } from "luxon" import { Formats } from "next-intl" import { datetime, RRule } from 'rrule' import { twMerge } from "tailwind-merge" -import { PomodoroAtom } from "./atoms" import { DUE_MAP, INITIAL_DUE, RECURRENCE_RULE_MAP } from "./constants" export function cn(...inputs: ClassValue[]) {