diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a301bc..b47b2e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Version 0.2.10 + +### Improved + +* performance optimization: faster load time for large data set + ## Version 0.2.9 ### Added diff --git a/components/DailyOverview.tsx b/components/DailyOverview.tsx index db4dc09..59b7c5d 100644 --- a/components/DailyOverview.tsx +++ b/components/DailyOverview.tsx @@ -6,7 +6,7 @@ import { ContextMenuItem, ContextMenuTrigger, } from "@/components/ui/context-menu" -import { cn, getHabitFreq } from '@/lib/utils' +import { cn } from '@/lib/utils' import Link from 'next/link' import { useState } from 'react' import { useAtom } from 'jotai' @@ -57,7 +57,7 @@ const ItemSection = ({ settings, setBrowserSettings, }: ItemSectionProps) => { - const { completeHabit, undoComplete, saveHabit } = useHabits(); + const { completeHabit, undoComplete, saveHabit, habitFreqMap } = useHabits(); const [_, setPomo] = useAtom(pomodoroAtom); if (items.length === 0) { @@ -117,8 +117,8 @@ const ItemSection = ({ } // Then by frequency (daily first) - const aFreq = getHabitFreq(a); - const bFreq = getHabitFreq(b); + const aFreq = habitFreqMap.get(a.id) || 'daily'; + const bFreq = habitFreqMap.get(b.id) || 'daily'; const freqOrder = ['daily', 'weekly', 'monthly', 'yearly']; if (freqOrder.indexOf(aFreq) !== freqOrder.indexOf(bFreq)) { return freqOrder.indexOf(aFreq) - freqOrder.indexOf(bFreq); @@ -213,7 +213,7 @@ const ItemSection = ({ {habit.isTask && ( { - saveHabit({...habit, frequency: d2t({ dateTime: getNow({ timezone: settings.system.timezone })})}) + saveHabit({ ...habit, frequency: d2t({ dateTime: getNow({ timezone: settings.system.timezone }) }) }) }}> Move to Today @@ -243,9 +243,9 @@ const ItemSection = ({ {completionsToday}/{target} )} - {getHabitFreq(habit) !== 'daily' && ( + {habitFreqMap.get(habit.id) !== 'daily' && ( - {getHabitFreq(habit)} + {habitFreqMap.get(habit.id)} )} diff --git a/components/HabitStreak.tsx b/components/HabitStreak.tsx index 86cecdf..9462283 100644 --- a/components/HabitStreak.tsx +++ b/components/HabitStreak.tsx @@ -2,10 +2,10 @@ import { Habit } from '@/lib/types' 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 { useAtom } from 'jotai' -import { settingsAtom, hasTasksAtom } from '@/lib/atoms' +import { settingsAtom, hasTasksAtom, completedHabitsMapAtom } from '@/lib/atoms' // Added completedHabitsMapAtom interface HabitStreakProps { habits: Habit[] @@ -14,6 +14,8 @@ interface HabitStreakProps { export default function HabitStreak({ habits }: HabitStreakProps) { const [settings] = useAtom(settingsAtom) const [hasTasks] = useAtom(hasTasksAtom) + const [completedHabitsMap] = useAtom(completedHabitsMapAtom) // Use the atom + // Get the last 7 days of data const dates = Array.from({ length: 7 }, (_, i) => { const d = getNow({ timezone: settings.system.timezone }); @@ -21,20 +23,17 @@ export default function HabitStreak({ habits }: HabitStreakProps) { }).reverse() const completions = dates.map(date => { - const completedHabits = getCompletedHabitsForDate({ - habits: habits.filter(h => !h.isTask), - date: t2d({ timestamp: date, timezone: settings.system.timezone }), - timezone: settings.system.timezone - }); - const completedTasks = getCompletedHabitsForDate({ - habits: habits.filter(h => h.isTask), - date: t2d({ timestamp: date, timezone: settings.system.timezone }), - timezone: settings.system.timezone - }); + // Get completed habits for the date from the map + const completedOnDate = completedHabitsMap.get(date) || []; + + // Filter the completed list to count habits and tasks + const completedHabitsCount = completedOnDate.filter(h => !h.isTask).length; + const completedTasksCount = completedOnDate.filter(h => h.isTask).length; + return { date, - habits: completedHabits.length, - tasks: completedTasks.length + habits: completedHabitsCount, + tasks: completedTasksCount }; }); diff --git a/hooks/useCoins.tsx b/hooks/useCoins.tsx index fd22816..02e94fa 100644 --- a/hooks/useCoins.tsx +++ b/hooks/useCoins.tsx @@ -2,14 +2,14 @@ import { useAtom } from 'jotai' import { calculateCoinsEarnedToday, calculateCoinsSpentToday, calculateTotalEarned, calculateTotalSpent, calculateTransactionsToday, checkPermission } from '@/lib/utils' import { coinsAtom, - // coinsEarnedTodayAtom, - // totalEarnedAtom, - // totalSpentAtom, - // coinsSpentTodayAtom, - // transactionsTodayAtom, - // coinsBalanceAtom, + coinsEarnedTodayAtom, + totalEarnedAtom, + totalSpentAtom, + coinsSpentTodayAtom, + transactionsTodayAtom, + coinsBalanceAtom, settingsAtom, - usersAtom + usersAtom, } from '@/lib/atoms' import { addCoins, removeCoins, saveCoinsData } from '@/app/actions/data' import { CoinsData, User } from '@/lib/types' @@ -29,7 +29,7 @@ function handlePermissionCheck( }) return false } - + if (!user.isAdmin && !checkPermission(user.permissions, resource, action)) { toast({ title: "Permission Denied", @@ -38,7 +38,7 @@ function handlePermissionCheck( }) return false } - + return true } @@ -57,12 +57,12 @@ export function useCoins(options?: { selectedUser?: string }) { // Filter transactions for the selectd user const transactions = coins.transactions.filter(t => t.userId === user?.id) - const balance = transactions.reduce((sum, t) => sum + t.amount, 0) - const coinsEarnedToday = calculateCoinsEarnedToday(transactions, settings.system.timezone) - const totalEarned = calculateTotalEarned(transactions) - const totalSpent = calculateTotalSpent(transactions) - const coinsSpentToday = calculateCoinsSpentToday(transactions, settings.system.timezone) - const transactionsToday = calculateTransactionsToday(transactions, settings.system.timezone) + const [balance] = useAtom(coinsBalanceAtom) + const [coinsEarnedToday] = useAtom(coinsEarnedTodayAtom) + const [totalEarned] = useAtom(totalEarnedAtom) + const [totalSpent] = useAtom(totalSpentAtom) + const [coinsSpentToday] = useAtom(coinsSpentTodayAtom) + const [transactionsToday] = useAtom(transactionsTodayAtom) const add = async (amount: number, description: string, note?: string) => { if (!handlePermissionCheck(currentUser, 'coins', 'write')) return null diff --git a/hooks/useHabits.tsx b/hooks/useHabits.tsx index 085bce8..c4721e4 100644 --- a/hooks/useHabits.tsx +++ b/hooks/useHabits.tsx @@ -1,5 +1,5 @@ -import { useAtom } from 'jotai' -import { habitsAtom, coinsAtom, settingsAtom, usersAtom } from '@/lib/atoms' +import { useAtom, atom } from 'jotai' +import { habitsAtom, coinsAtom, settingsAtom, usersAtom, habitFreqMapAtom } from '@/lib/atoms' import { addCoins, removeCoins, saveHabitsData } from '@/app/actions/data' import { Habit, Permission, SafeUser, User } from '@/lib/types' import { toast } from '@/hooks/use-toast' @@ -34,7 +34,7 @@ function handlePermissionCheck( }) return false } - + if (!user.isAdmin && !checkPermission(user.permissions, resource, action)) { toast({ title: "Permission Denied", @@ -43,7 +43,7 @@ function handlePermissionCheck( }) return false } - + return true } @@ -54,6 +54,7 @@ export function useHabits() { const [habitsData, setHabitsData] = useAtom(habitsAtom) const [coins, setCoins] = useAtom(coinsAtom) const [settings] = useAtom(settingsAtom) + const [habitFreqMap] = useAtom(habitFreqMapAtom) const completeHabit = async (habit: Habit) => { if (!handlePermissionCheck(currentUser, 'habit', 'interact')) return @@ -313,6 +314,7 @@ export function useHabits() { deleteHabit, completePastHabit, archiveHabit, - unarchiveHabit + unarchiveHabit, + habitFreqMap, } } diff --git a/lib/atoms.ts b/lib/atoms.ts index 25aa7cd..2302d19 100644 --- a/lib/atoms.ts +++ b/lib/atoms.ts @@ -24,10 +24,12 @@ import { getISODate, isHabitDueToday, getNow, - isHabitDue + isHabitDue, + getHabitFreq } from "@/lib/utils"; import { atomFamily, atomWithStorage } from "jotai/utils"; import { DateTime } from "luxon"; +import { Freq } from "./types"; export interface BrowserSettings { viewType: ViewType @@ -50,44 +52,44 @@ export const coinsAtom = atom(getDefaultCoinsData()); export const wishlistAtom = atom(getDefaultWishlistData()); export const serverSettingsAtom = atom(getDefaultServerSettings()); -// // Derived atom for coins earned today -// export const coinsEarnedTodayAtom = atom((get) => { -// const coins = get(coinsAtom); -// const settings = get(settingsAtom); -// return calculateCoinsEarnedToday(coins.transactions, settings.system.timezone); -// }); +// Derived atom for coins earned today +export const coinsEarnedTodayAtom = atom((get) => { + const coins = get(coinsAtom); + const settings = get(settingsAtom); + return calculateCoinsEarnedToday(coins.transactions, settings.system.timezone); +}); -// // Derived atom for total earned -// export const totalEarnedAtom = atom((get) => { -// const coins = get(coinsAtom); -// return calculateTotalEarned(coins.transactions); -// }); +// Derived atom for total earned +export const totalEarnedAtom = atom((get) => { + const coins = get(coinsAtom); + return calculateTotalEarned(coins.transactions); +}); -// // Derived atom for total spent -// export const totalSpentAtom = atom((get) => { -// const coins = get(coinsAtom); -// return calculateTotalSpent(coins.transactions); -// }); +// Derived atom for total spent +export const totalSpentAtom = atom((get) => { + const coins = get(coinsAtom); + return calculateTotalSpent(coins.transactions); +}); -// // Derived atom for coins spent today -// export const coinsSpentTodayAtom = atom((get) => { -// const coins = get(coinsAtom); -// const settings = get(settingsAtom); -// return calculateCoinsSpentToday(coins.transactions, settings.system.timezone); -// }); +// Derived atom for coins spent today +export const coinsSpentTodayAtom = atom((get) => { + const coins = get(coinsAtom); + const settings = get(settingsAtom); + return calculateCoinsSpentToday(coins.transactions, settings.system.timezone); +}); -// // 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); -// }); +// 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); +}); -// // Derived atom for current balance from all transactions -// export const coinsBalanceAtom = atom((get) => { -// const coins = get(coinsAtom); -// return coins.transactions.reduce((sum, transaction) => sum + transaction.amount, 0); -// }); +// Derived atom for current balance from all transactions +export const coinsBalanceAtom = atom((get) => { + const coins = get(coinsAtom); + return coins.transactions.reduce((sum, transaction) => sum + transaction.amount, 0); +}); /* transient atoms */ interface PomodoroAtom { @@ -150,6 +152,15 @@ export const completedHabitsMapAtom = atom((get) => { return map; }); +// Derived atom for habit frequency map +export const habitFreqMapAtom = atom((get) => { + const habits = get(habitsAtom).habits; + const map = new Map(); + habits.forEach(habit => { + map.set(habit.id, getHabitFreq(habit)); + }); + return map; +}); export const pomodoroTodayCompletionsAtom = atom((get) => { const pomo = get(pomodoroAtom) diff --git a/package.json b/package.json index 1e1a3b4..6611e93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "habittrove", - "version": "0.2.9", + "version": "0.2.10", "private": true, "scripts": { "dev": "next dev --turbopack",