mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-01-20 22:24:28 +01:00
performance optimization via atoms (#108)
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## Version 0.2.10
|
||||
|
||||
### Improved
|
||||
|
||||
* performance optimization: faster load time for large data set
|
||||
|
||||
## Version 0.2.9
|
||||
|
||||
### Added
|
||||
|
||||
@@ -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 = ({
|
||||
</ContextMenuItem>
|
||||
{habit.isTask && (
|
||||
<ContextMenuItem onClick={() => {
|
||||
saveHabit({...habit, frequency: d2t({ dateTime: getNow({ timezone: settings.system.timezone })})})
|
||||
saveHabit({ ...habit, frequency: d2t({ dateTime: getNow({ timezone: settings.system.timezone }) }) })
|
||||
}}>
|
||||
<Calendar className="mr-2 h-4 w-4" />
|
||||
<span>Move to Today</span>
|
||||
@@ -243,9 +243,9 @@ const ItemSection = ({
|
||||
{completionsToday}/{target}
|
||||
</span>
|
||||
)}
|
||||
{getHabitFreq(habit) !== 'daily' && (
|
||||
{habitFreqMap.get(habit.id) !== 'daily' && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{getHabitFreq(habit)}
|
||||
{habitFreqMap.get(habit.id)}
|
||||
</Badge>
|
||||
)}
|
||||
<span className="flex items-center">
|
||||
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
79
lib/atoms.ts
79
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<string, Freq>();
|
||||
habits.forEach(habit => {
|
||||
map.set(habit.id, getHabitFreq(habit));
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
export const pomodoroTodayCompletionsAtom = atom((get) => {
|
||||
const pomo = get(pomodoroAtom)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "habittrove",
|
||||
"version": "0.2.9",
|
||||
"version": "0.2.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
|
||||
Reference in New Issue
Block a user