mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-01-21 06:34:30 +01:00
performance optimization via atoms (#108)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -213,7 +213,7 @@ const ItemSection = ({
|
|||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
{habit.isTask && (
|
{habit.isTask && (
|
||||||
<ContextMenuItem onClick={() => {
|
<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" />
|
<Calendar className="mr-2 h-4 w-4" />
|
||||||
<span>Move to Today</span>
|
<span>Move to Today</span>
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -29,7 +29,7 @@ function handlePermissionCheck(
|
|||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.isAdmin && !checkPermission(user.permissions, resource, action)) {
|
if (!user.isAdmin && !checkPermission(user.permissions, resource, action)) {
|
||||||
toast({
|
toast({
|
||||||
title: "Permission Denied",
|
title: "Permission Denied",
|
||||||
@@ -38,7 +38,7 @@ function handlePermissionCheck(
|
|||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -34,7 +34,7 @@ function handlePermissionCheck(
|
|||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.isAdmin && !checkPermission(user.permissions, resource, action)) {
|
if (!user.isAdmin && !checkPermission(user.permissions, resource, action)) {
|
||||||
toast({
|
toast({
|
||||||
title: "Permission Denied",
|
title: "Permission Denied",
|
||||||
@@ -43,7 +43,7 @@ function handlePermissionCheck(
|
|||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
lib/atoms.ts
79
lib/atoms.ts
@@ -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)
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user