performance optimization via atoms (#108)

This commit is contained in:
Doh
2025-04-20 12:14:51 -04:00
committed by GitHub
parent dda8b522e3
commit 2408ed84bd
7 changed files with 94 additions and 76 deletions

View File

@@ -1,5 +1,11 @@
# Changelog # Changelog
## Version 0.2.10
### Improved
* performance optimization: faster load time for large data set
## Version 0.2.9 ## Version 0.2.9
### Added ### Added

View File

@@ -6,7 +6,7 @@ import {
ContextMenuItem, ContextMenuItem,
ContextMenuTrigger, ContextMenuTrigger,
} from "@/components/ui/context-menu" } from "@/components/ui/context-menu"
import { cn, getHabitFreq } from '@/lib/utils' import { cn } from '@/lib/utils'
import Link from 'next/link' import Link from 'next/link'
import { useState } from 'react' import { useState } from 'react'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
@@ -57,7 +57,7 @@ const ItemSection = ({
settings, settings,
setBrowserSettings, setBrowserSettings,
}: ItemSectionProps) => { }: ItemSectionProps) => {
const { completeHabit, undoComplete, saveHabit } = useHabits(); const { completeHabit, undoComplete, saveHabit, habitFreqMap } = useHabits();
const [_, setPomo] = useAtom(pomodoroAtom); const [_, setPomo] = useAtom(pomodoroAtom);
if (items.length === 0) { if (items.length === 0) {
@@ -117,8 +117,8 @@ const ItemSection = ({
} }
// Then by frequency (daily first) // Then by frequency (daily first)
const aFreq = getHabitFreq(a); const aFreq = habitFreqMap.get(a.id) || 'daily';
const bFreq = getHabitFreq(b); const bFreq = habitFreqMap.get(b.id) || 'daily';
const freqOrder = ['daily', 'weekly', 'monthly', 'yearly']; const freqOrder = ['daily', 'weekly', 'monthly', 'yearly'];
if (freqOrder.indexOf(aFreq) !== freqOrder.indexOf(bFreq)) { if (freqOrder.indexOf(aFreq) !== freqOrder.indexOf(bFreq)) {
return freqOrder.indexOf(aFreq) - freqOrder.indexOf(bFreq); return freqOrder.indexOf(aFreq) - freqOrder.indexOf(bFreq);
@@ -243,9 +243,9 @@ const ItemSection = ({
{completionsToday}/{target} {completionsToday}/{target}
</span> </span>
)} )}
{getHabitFreq(habit) !== 'daily' && ( {habitFreqMap.get(habit.id) !== 'daily' && (
<Badge variant="outline" className="text-xs"> <Badge variant="outline" className="text-xs">
{getHabitFreq(habit)} {habitFreqMap.get(habit.id)}
</Badge> </Badge>
)} )}
<span className="flex items-center"> <span className="flex items-center">

View File

@@ -2,10 +2,10 @@
import { Habit } from '@/lib/types' import { Habit } from '@/lib/types'
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { d2s, getNow, t2d, getCompletedHabitsForDate } from '@/lib/utils' import { d2s, getNow, t2d } from '@/lib/utils' // Removed getCompletedHabitsForDate
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { settingsAtom, hasTasksAtom } from '@/lib/atoms' import { settingsAtom, hasTasksAtom, completedHabitsMapAtom } from '@/lib/atoms' // Added completedHabitsMapAtom
interface HabitStreakProps { interface HabitStreakProps {
habits: Habit[] habits: Habit[]
@@ -14,6 +14,8 @@ interface HabitStreakProps {
export default function HabitStreak({ habits }: HabitStreakProps) { export default function HabitStreak({ habits }: HabitStreakProps) {
const [settings] = useAtom(settingsAtom) const [settings] = useAtom(settingsAtom)
const [hasTasks] = useAtom(hasTasksAtom) const [hasTasks] = useAtom(hasTasksAtom)
const [completedHabitsMap] = useAtom(completedHabitsMapAtom) // Use the atom
// Get the last 7 days of data // Get the last 7 days of data
const dates = Array.from({ length: 7 }, (_, i) => { const dates = Array.from({ length: 7 }, (_, i) => {
const d = getNow({ timezone: settings.system.timezone }); const d = getNow({ timezone: settings.system.timezone });
@@ -21,20 +23,17 @@ export default function HabitStreak({ habits }: HabitStreakProps) {
}).reverse() }).reverse()
const completions = dates.map(date => { const completions = dates.map(date => {
const completedHabits = getCompletedHabitsForDate({ // Get completed habits for the date from the map
habits: habits.filter(h => !h.isTask), const completedOnDate = completedHabitsMap.get(date) || [];
date: t2d({ timestamp: date, timezone: settings.system.timezone }),
timezone: settings.system.timezone // Filter the completed list to count habits and tasks
}); const completedHabitsCount = completedOnDate.filter(h => !h.isTask).length;
const completedTasks = getCompletedHabitsForDate({ const completedTasksCount = completedOnDate.filter(h => h.isTask).length;
habits: habits.filter(h => h.isTask),
date: t2d({ timestamp: date, timezone: settings.system.timezone }),
timezone: settings.system.timezone
});
return { return {
date, date,
habits: completedHabits.length, habits: completedHabitsCount,
tasks: completedTasks.length tasks: completedTasksCount
}; };
}); });

View File

@@ -2,14 +2,14 @@ import { useAtom } from 'jotai'
import { calculateCoinsEarnedToday, calculateCoinsSpentToday, calculateTotalEarned, calculateTotalSpent, calculateTransactionsToday, checkPermission } from '@/lib/utils' import { calculateCoinsEarnedToday, calculateCoinsSpentToday, calculateTotalEarned, calculateTotalSpent, calculateTransactionsToday, checkPermission } from '@/lib/utils'
import { import {
coinsAtom, coinsAtom,
// coinsEarnedTodayAtom, coinsEarnedTodayAtom,
// totalEarnedAtom, totalEarnedAtom,
// totalSpentAtom, totalSpentAtom,
// coinsSpentTodayAtom, coinsSpentTodayAtom,
// transactionsTodayAtom, transactionsTodayAtom,
// coinsBalanceAtom, coinsBalanceAtom,
settingsAtom, settingsAtom,
usersAtom usersAtom,
} from '@/lib/atoms' } from '@/lib/atoms'
import { addCoins, removeCoins, saveCoinsData } from '@/app/actions/data' import { addCoins, removeCoins, saveCoinsData } from '@/app/actions/data'
import { CoinsData, User } from '@/lib/types' import { CoinsData, User } from '@/lib/types'
@@ -57,12 +57,12 @@ export function useCoins(options?: { selectedUser?: string }) {
// Filter transactions for the selectd user // Filter transactions for the selectd user
const transactions = coins.transactions.filter(t => t.userId === user?.id) const transactions = coins.transactions.filter(t => t.userId === user?.id)
const balance = transactions.reduce((sum, t) => sum + t.amount, 0) const [balance] = useAtom(coinsBalanceAtom)
const coinsEarnedToday = calculateCoinsEarnedToday(transactions, settings.system.timezone) const [coinsEarnedToday] = useAtom(coinsEarnedTodayAtom)
const totalEarned = calculateTotalEarned(transactions) const [totalEarned] = useAtom(totalEarnedAtom)
const totalSpent = calculateTotalSpent(transactions) const [totalSpent] = useAtom(totalSpentAtom)
const coinsSpentToday = calculateCoinsSpentToday(transactions, settings.system.timezone) const [coinsSpentToday] = useAtom(coinsSpentTodayAtom)
const transactionsToday = calculateTransactionsToday(transactions, settings.system.timezone) const [transactionsToday] = useAtom(transactionsTodayAtom)
const add = async (amount: number, description: string, note?: string) => { const add = async (amount: number, description: string, note?: string) => {
if (!handlePermissionCheck(currentUser, 'coins', 'write')) return null if (!handlePermissionCheck(currentUser, 'coins', 'write')) return null

View File

@@ -1,5 +1,5 @@
import { useAtom } from 'jotai' import { useAtom, atom } from 'jotai'
import { habitsAtom, coinsAtom, settingsAtom, usersAtom } from '@/lib/atoms' import { habitsAtom, coinsAtom, settingsAtom, usersAtom, habitFreqMapAtom } from '@/lib/atoms'
import { addCoins, removeCoins, saveHabitsData } from '@/app/actions/data' import { addCoins, removeCoins, saveHabitsData } from '@/app/actions/data'
import { Habit, Permission, SafeUser, User } from '@/lib/types' import { Habit, Permission, SafeUser, User } from '@/lib/types'
import { toast } from '@/hooks/use-toast' import { toast } from '@/hooks/use-toast'
@@ -54,6 +54,7 @@ export function useHabits() {
const [habitsData, setHabitsData] = useAtom(habitsAtom) const [habitsData, setHabitsData] = useAtom(habitsAtom)
const [coins, setCoins] = useAtom(coinsAtom) const [coins, setCoins] = useAtom(coinsAtom)
const [settings] = useAtom(settingsAtom) const [settings] = useAtom(settingsAtom)
const [habitFreqMap] = useAtom(habitFreqMapAtom)
const completeHabit = async (habit: Habit) => { const completeHabit = async (habit: Habit) => {
if (!handlePermissionCheck(currentUser, 'habit', 'interact')) return if (!handlePermissionCheck(currentUser, 'habit', 'interact')) return
@@ -313,6 +314,7 @@ export function useHabits() {
deleteHabit, deleteHabit,
completePastHabit, completePastHabit,
archiveHabit, archiveHabit,
unarchiveHabit unarchiveHabit,
habitFreqMap,
} }
} }

View File

@@ -24,10 +24,12 @@ import {
getISODate, getISODate,
isHabitDueToday, isHabitDueToday,
getNow, getNow,
isHabitDue isHabitDue,
getHabitFreq
} from "@/lib/utils"; } from "@/lib/utils";
import { atomFamily, atomWithStorage } from "jotai/utils"; import { atomFamily, atomWithStorage } from "jotai/utils";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { Freq } from "./types";
export interface BrowserSettings { export interface BrowserSettings {
viewType: ViewType viewType: ViewType
@@ -50,44 +52,44 @@ export const coinsAtom = atom(getDefaultCoinsData());
export const wishlistAtom = atom(getDefaultWishlistData()); export const wishlistAtom = atom(getDefaultWishlistData());
export const serverSettingsAtom = atom(getDefaultServerSettings()); export const serverSettingsAtom = atom(getDefaultServerSettings());
// // Derived atom for coins earned today // Derived atom for coins earned today
// export const coinsEarnedTodayAtom = atom((get) => { export const coinsEarnedTodayAtom = atom((get) => {
// const coins = get(coinsAtom); const coins = get(coinsAtom);
// const settings = get(settingsAtom); const settings = get(settingsAtom);
// return calculateCoinsEarnedToday(coins.transactions, settings.system.timezone); return calculateCoinsEarnedToday(coins.transactions, settings.system.timezone);
// }); });
// // Derived atom for total earned // Derived atom for total earned
// export const totalEarnedAtom = atom((get) => { export const totalEarnedAtom = atom((get) => {
// const coins = get(coinsAtom); const coins = get(coinsAtom);
// return calculateTotalEarned(coins.transactions); return calculateTotalEarned(coins.transactions);
// }); });
// // Derived atom for total spent // Derived atom for total spent
// export const totalSpentAtom = atom((get) => { export const totalSpentAtom = atom((get) => {
// const coins = get(coinsAtom); const coins = get(coinsAtom);
// return calculateTotalSpent(coins.transactions); return calculateTotalSpent(coins.transactions);
// }); });
// // Derived atom for coins spent today // Derived atom for coins spent today
// export const coinsSpentTodayAtom = atom((get) => { export const coinsSpentTodayAtom = atom((get) => {
// const coins = get(coinsAtom); const coins = get(coinsAtom);
// const settings = get(settingsAtom); const settings = get(settingsAtom);
// return calculateCoinsSpentToday(coins.transactions, settings.system.timezone); return calculateCoinsSpentToday(coins.transactions, settings.system.timezone);
// }); });
// // Derived atom for transactions today // Derived atom for transactions today
// export const transactionsTodayAtom = atom((get) => { export const transactionsTodayAtom = atom((get) => {
// const coins = get(coinsAtom); const coins = get(coinsAtom);
// const settings = get(settingsAtom); const settings = get(settingsAtom);
// return calculateTransactionsToday(coins.transactions, settings.system.timezone); return calculateTransactionsToday(coins.transactions, settings.system.timezone);
// }); });
// // Derived atom for current balance from all transactions // Derived atom for current balance from all transactions
// export const coinsBalanceAtom = atom((get) => { export const coinsBalanceAtom = atom((get) => {
// const coins = get(coinsAtom); const coins = get(coinsAtom);
// return coins.transactions.reduce((sum, transaction) => sum + transaction.amount, 0); return coins.transactions.reduce((sum, transaction) => sum + transaction.amount, 0);
// }); });
/* transient atoms */ /* transient atoms */
interface PomodoroAtom { interface PomodoroAtom {
@@ -150,6 +152,15 @@ export const completedHabitsMapAtom = atom((get) => {
return map; return map;
}); });
// Derived atom for habit frequency map
export const habitFreqMapAtom = atom((get) => {
const habits = get(habitsAtom).habits;
const map = new Map<string, Freq>();
habits.forEach(habit => {
map.set(habit.id, getHabitFreq(habit));
});
return map;
});
export const pomodoroTodayCompletionsAtom = atom((get) => { export const pomodoroTodayCompletionsAtom = atom((get) => {
const pomo = get(pomodoroAtom) const pomo = get(pomodoroAtom)

View File

@@ -1,6 +1,6 @@
{ {
"name": "habittrove", "name": "habittrove",
"version": "0.2.9", "version": "0.2.10",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "next dev --turbopack",