mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-03-08 03:29:49 +01:00
fix: migrate atoms to normal functions
This commit is contained in:
@@ -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() {
|
||||
<div className="p-4 rounded-lg bg-orange-100 dark:bg-orange-900">
|
||||
<div className="text-sm text-orange-800 dark:text-orange-100 mb-1">{t('todaysTransactionsLabel')}</div>
|
||||
<div className="text-2xl font-bold text-orange-900 dark:text-orange-50">
|
||||
{transactionsToday} 📊
|
||||
{calculateTransactionsToday(transactions, settings.system.timezone)} 📊
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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<number>(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
|
||||
}
|
||||
}
|
||||
|
||||
86
lib/atoms.ts
86
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<UserData>())
|
||||
export const currentUserIdAtom = atom<UserId | undefined>(undefined);
|
||||
export const settingsAtom = atom(getDefaultSettings<Settings>());
|
||||
export const habitsAtom = atom(getDefaultHabitsData<HabitsData>());
|
||||
export const coinsAtom = atom(getDefaultCoinsData<CoinsData>());
|
||||
export const wishlistAtom = atom(getDefaultWishlistData<WishlistData>());
|
||||
export const serverSettingsAtom = atom(getDefaultServerSettings<ServerSettings>());
|
||||
export const userSelectAtom = atom<boolean>(false)
|
||||
export const aboutOpenAtom = atom<boolean>(false)
|
||||
|
||||
export const pomodoroAtom = atom<PomodoroAtom>({
|
||||
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<UserId | undefined>(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<PomodoroAtom>({
|
||||
show: false,
|
||||
selectedHabitId: null,
|
||||
autoStart: true,
|
||||
minimized: false,
|
||||
})
|
||||
|
||||
export const userSelectAtom = atom<boolean>(false)
|
||||
export const aboutOpenAtom = atom<boolean>(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<string, Habit[]>();
|
||||
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<string, Habit[]>();
|
||||
|
||||
// For each date in the cache
|
||||
Object.entries(completionCache).forEach(([dateKey, habitCompletions]) => {
|
||||
const completedHabits = habits.filter(habit => {
|
||||
|
||||
13
lib/types.ts
13
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
|
||||
}
|
||||
@@ -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[]) {
|
||||
|
||||
Reference in New Issue
Block a user