fix: migrate atoms to normal functions

This commit is contained in:
2026-03-06 22:47:32 +01:00
parent c418bddd9e
commit f7034116a3
7 changed files with 39 additions and 61 deletions

View File

@@ -13,22 +13,22 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip" } from "@/components/ui/tooltip"
import { useHabits } from '@/hooks/useHabits' import { useHabits } from '@/hooks/useHabits'
import { browserSettingsAtom, completedHabitsMapAtom, hasTasksAtom, pomodoroAtom, settingsAtom } from '@/lib/atoms' import { browserSettingsAtom, completedHabitsMapAtom, settingsAtom } from '@/lib/atoms'
import { DESKTOP_DISPLAY_ITEM_COUNT } from '@/lib/constants' import { DESKTOP_DISPLAY_ITEM_COUNT } from '@/lib/constants'
import { Habit, WishlistItemType } from '@/lib/types' import { Habit, WishlistItemType } from '@/lib/types'
import { cn, d2t, getNow, getTodayInTimezone, isHabitDue, isSameDate, isTaskOverdue, t2d } from '@/lib/utils' import { cn, d2t, getNow, getTodayInTimezone, isHabitDue, isSameDate, isTaskOverdue, t2d } from '@/lib/utils'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { AlertTriangle, ArrowRight, ChevronDown, ChevronUp, Circle, CircleCheck, Coins, Pin, Plus } from 'lucide-react'; // Removed unused icons import { AlertTriangle, ArrowRight, ChevronDown, ChevronUp, Circle, CircleCheck, Coins, Pin, Plus } from 'lucide-react';
import { useTranslations } from 'next-intl' import { useTranslations } from 'next-intl'
import Link from 'next/link' import Link from 'next/link'
import { useState } from 'react' import { useState } from 'react'
import AddEditHabitModal from './AddEditHabitModal' import AddEditHabitModal from './AddEditHabitModal'
import CompletionCountBadge from './CompletionCountBadge' import CompletionCountBadge from './CompletionCountBadge'
import ConfirmDialog from './ConfirmDialog' import ConfirmDialog from './ConfirmDialog'
import DrawingDisplay from './DrawingDisplay'
import { HabitContextMenuItems } from './HabitContextMenuItems' import { HabitContextMenuItems } from './HabitContextMenuItems'
import Linkify from './linkify' import Linkify from './linkify'
import { Button } from './ui/button' import { Button } from './ui/button'
import DrawingDisplay from './DrawingDisplay'
interface UpcomingItemsProps { interface UpcomingItemsProps {
habits: Habit[] habits: Habit[]
@@ -54,8 +54,7 @@ const ItemSection = ({
addNewItem, addNewItem,
}: ItemSectionProps) => { }: ItemSectionProps) => {
const t = useTranslations('DailyOverview'); const t = useTranslations('DailyOverview');
const { completeHabit, undoComplete, saveHabit, deleteHabit, archiveHabit, habitFreqMap } = useHabits(); const { completeHabit, undoComplete, saveHabit, deleteHabit, habitFreqMap } = useHabits();
const [_, setPomo] = useAtom(pomodoroAtom);
const [browserSettings, setBrowserSettings] = useAtom(browserSettingsAtom); const [browserSettings, setBrowserSettings] = useAtom(browserSettingsAtom);
const [settings] = useAtom(settingsAtom); const [settings] = useAtom(settingsAtom);
const [completedHabitsMap] = useAtom(completedHabitsMapAtom); const [completedHabitsMap] = useAtom(completedHabitsMapAtom);
@@ -397,8 +396,6 @@ export default function DailyOverview({
return a.coinCost - b.coinCost return a.coinCost - b.coinCost
}) })
const [hasTasks] = useAtom(hasTasksAtom)
const [, setPomo] = useAtom(pomodoroAtom)
const [modalConfig, setModalConfig] = useState<{ const [modalConfig, setModalConfig] = useState<{
isOpen: boolean, isOpen: boolean,
isTask: boolean isTask: boolean
@@ -416,7 +413,7 @@ export default function DailyOverview({
<CardContent> <CardContent>
<div className="space-y-6"> <div className="space-y-6">
{/* Tasks Section */} {/* Tasks Section */}
{hasTasks && ( {habits.some(habit => habit.isTask === true) && (
<ItemSection <ItemSection
title={t('dailyTasksTitle')} title={t('dailyTasksTitle')}
items={dailyTasks} items={dailyTasks}

View File

@@ -4,7 +4,7 @@ import CompletionCountBadge from '@/components/CompletionCountBadge'
import { Calendar } from '@/components/ui/calendar' import { Calendar } from '@/components/ui/calendar'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { useHabits } from '@/hooks/useHabits' import { useHabits } from '@/hooks/useHabits'
import { completedHabitsMapAtom, habitsAtom, hasTasksAtom, settingsAtom } from '@/lib/atoms' import { completedHabitsMapAtom, habitsAtom, settingsAtom } from '@/lib/atoms'
import { Habit } from '@/lib/types' import { Habit } from '@/lib/types'
import { d2s, getCompletionsForDate, getISODate, getNow, isHabitDue } from '@/lib/utils' import { d2s, getCompletionsForDate, getISODate, getNow, isHabitDue } from '@/lib/utils'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
@@ -29,7 +29,6 @@ export default function HabitCalendar() {
const [selectedDateTime, setSelectedDateTime] = useState<DateTime>(getNow({ timezone: settings.system.timezone })) const [selectedDateTime, setSelectedDateTime] = useState<DateTime>(getNow({ timezone: settings.system.timezone }))
const selectedDate = selectedDateTime.toFormat("yyyy-MM-dd") const selectedDate = selectedDateTime.toFormat("yyyy-MM-dd")
const [habitsData] = useAtom(habitsAtom) const [habitsData] = useAtom(habitsAtom)
const [hasTasks] = useAtom(hasTasksAtom)
const habits = habitsData.habits const habits = habitsData.habits
const [completedHabitsMap] = useAtom(completedHabitsMapAtom) const [completedHabitsMap] = useAtom(completedHabitsMapAtom)
@@ -83,7 +82,7 @@ export default function HabitCalendar() {
<CardContent> <CardContent>
{selectedDateTime && ( {selectedDateTime && (
<div className="space-y-8"> <div className="space-y-8">
{hasTasks && ( {habits.some(habit => habit.isTask === true) && (
<div className="pt-2 border-t"> <div className="pt-2 border-t">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<h3 className="font-medium text-sm text-muted-foreground uppercase tracking-wide">{t('tasksSectionTitle')}</h3> <h3 className="font-medium text-sm text-muted-foreground uppercase tracking-wide">{t('tasksSectionTitle')}</h3>

View File

@@ -1,22 +1,16 @@
'use client' 'use client'
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { completedHabitsMapAtom, hasTasksAtom, settingsAtom } from '@/lib/atoms'; // Added completedHabitsMapAtom import { completedHabitsMapAtom, settingsAtom } from '@/lib/atoms';
import { Habit } from '@/lib/types'; import { Habit } from '@/lib/types';
import { d2s, getNow } from '@/lib/utils'; // Removed getCompletedHabitsForDate import { d2s, getNow } from '@/lib/utils';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; import { CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
export default function HabitStreak({ habits }: { habits: Habit[] }) {
interface HabitStreakProps {
habits: Habit[]
}
export default function HabitStreak({ habits }: HabitStreakProps) {
const t = useTranslations('HabitStreak'); const t = useTranslations('HabitStreak');
const [settings] = useAtom(settingsAtom) const [settings] = useAtom(settingsAtom)
const [hasTasks] = useAtom(hasTasksAtom)
const [completedHabitsMap] = useAtom(completedHabitsMapAtom) // Use the atom const [completedHabitsMap] = useAtom(completedHabitsMapAtom) // Use the atom
// Get the last 7 days of data // Get the last 7 days of data
@@ -72,7 +66,7 @@ export default function HabitStreak({ habits }: HabitStreakProps) {
strokeWidth={2} strokeWidth={2}
dot={false} dot={false}
/> />
{hasTasks && ( {habits.some(habit => habit.isTask === true) && (
<Line <Line
type="monotone" type="monotone"
name={t('tooltipTasksLabel')} name={t('tooltipTasksLabel')}

View File

@@ -3,8 +3,8 @@
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Progress } from '@/components/ui/progress' import { Progress } from '@/components/ui/progress'
import { useHabits } from '@/hooks/useHabits' import { useHabits } from '@/hooks/useHabits'
import { habitsAtom, pomodoroAtom, pomodoroTodayCompletionsAtom } from '@/lib/atoms' import { habitsAtom, pomodoroAtom, settingsAtom } from '@/lib/atoms'
import { cn } from '@/lib/utils' import { cn, getTodayCompletions } from '@/lib/utils'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { Clock, Minus, Pause, Play, RotateCw, SkipForward, X } from 'lucide-react' import { Clock, Minus, Pause, Play, RotateCw, SkipForward, X } from 'lucide-react'
import { useTranslations } from 'next-intl' import { useTranslations } from 'next-intl'
@@ -41,12 +41,13 @@ export default function PomodoroTimer() {
const [pomo, setPomo] = useAtom(pomodoroAtom) const [pomo, setPomo] = useAtom(pomodoroAtom)
const { show, selectedHabitId, autoStart, minimized } = pomo const { show, selectedHabitId, autoStart, minimized } = pomo
const [habitsData] = useAtom(habitsAtom) const [habitsData] = useAtom(habitsAtom)
const [settingsData] = useAtom(settingsAtom)
const { completeHabit } = useHabits() const { completeHabit } = useHabits()
const selectedHabit = selectedHabitId ? habitsData.habits.find(habit => habit.id === selectedHabitId) : null const selectedHabit = selectedHabitId ? habitsData.habits.find(habit => habit.id === selectedHabitId) : null
const [timeLeft, setTimeLeft] = useState(PomoConfigs.focus.duration) const [timeLeft, setTimeLeft] = useState(PomoConfigs.focus.duration)
const [state, setState] = useState<'started' | 'stopped' | 'paused'>(autoStart ? 'started' : 'stopped') const [state, setState] = useState<'started' | 'stopped' | 'paused'>(autoStart ? 'started' : 'stopped')
const wakeLock = useRef<WakeLockSentinel | null>(null) const wakeLock = useRef<WakeLockSentinel | null>(null)
const [todayCompletions] = useAtom(pomodoroTodayCompletionsAtom) const todayCompletions = getTodayCompletions(pomo, habitsData, settingsData);
const currentTimerRef = useRef<PomoConfig>(PomoConfigs.focus) const currentTimerRef = useRef<PomoConfig>(PomoConfigs.focus)
const [currentLabel, setCurrentLabel] = useState(() => { const [currentLabel, setCurrentLabel] = useState(() => {
const labels = currentTimerRef.current.getLabels(); const labels = currentTimerRef.current.getLabels();

View File

@@ -1,12 +1,13 @@
import { addCoins, removeCoins, saveHabitsData } from '@/app/actions/data' import { addCoins, removeCoins, saveHabitsData } from '@/app/actions/data'
import { ToastAction } from '@/components/ui/toast' import { ToastAction } from '@/components/ui/toast'
import { toast } from '@/hooks/use-toast' import { toast } from '@/hooks/use-toast'
import { coinsAtom, currentUserAtom, habitFreqMapAtom, habitsAtom, settingsAtom, usersAtom } from '@/lib/atoms' import { coinsAtom, currentUserAtom, habitsAtom, settingsAtom } from '@/lib/atoms'
import { Habit } from '@/lib/types' import { Freq, Habit } from '@/lib/types'
import { import {
d2s, d2s,
d2t, d2t,
getCompletionsForDate, getCompletionsForDate,
getHabitFreq,
getISODate, getISODate,
getNow, getNow,
getTodayInTimezone, getTodayInTimezone,
@@ -23,12 +24,15 @@ import { useTranslations } from 'next-intl'
export function useHabits() { export function useHabits() {
const t = useTranslations('useHabits'); const t = useTranslations('useHabits');
const tCommon = useTranslations('Common'); const tCommon = useTranslations('Common');
const [usersData] = useAtom(usersAtom)
const [currentUser] = useAtom(currentUserAtom) const [currentUser] = useAtom(currentUserAtom)
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 [habitFreqMap] = useAtom(habitFreqMapAtom)
const habitFreqMap = new Map<string, Freq>();
habitsData.habits.forEach(habit => {
habitFreqMap.set(habit.id, getHabitFreq(habit));
})
const completeHabit = async (habit: Habit) => { const completeHabit = async (habit: Habit) => {
if (!handlePermissionCheck(currentUser, 'habit', 'interact', tCommon)) return if (!handlePermissionCheck(currentUser, 'habit', 'interact', tCommon)) return

View File

@@ -114,7 +114,7 @@ export const coinsBalanceAtom = atom((get) => {
}); });
/* transient atoms */ /* transient atoms */
interface PomodoroAtom { export interface PomodoroAtom {
show: boolean show: boolean
selectedHabitId: string | null selectedHabitId: string | null
autoStart: boolean autoStart: boolean
@@ -191,38 +191,6 @@ 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) => {
const pomo = get(pomodoroAtom)
const habits = get(habitsAtom)
const settings = get(settingsAtom)
if (!pomo.selectedHabitId) return 0
const selectedHabit = habits.habits.find(h => h.id === pomo.selectedHabitId!)
if (!selectedHabit) return 0
return getCompletionsForToday({
habit: selectedHabit,
timezone: settings.system.timezone
})
})
// Derived atom to check if any habits are tasks
export const hasTasksAtom = atom((get) => {
const habits = get(habitsAtom)
return habits.habits.some(habit => habit.isTask === true)
})
// Atom family for habits by specific date // Atom family for habits by specific date
export const habitsByDateFamily = atomFamily((dateString: string) => export const habitsByDateFamily = atomFamily((dateString: string) =>
atom((get) => { atom((get) => {

View File

@@ -6,6 +6,7 @@ import { DateTime, DateTimeFormatOptions } from "luxon"
import { Formats } from "next-intl" import { Formats } from "next-intl"
import { datetime, RRule } from 'rrule' import { datetime, RRule } from 'rrule'
import { twMerge } from "tailwind-merge" import { twMerge } from "tailwind-merge"
import { PomodoroAtom } from "./atoms"
import { DUE_MAP, INITIAL_DUE, RECURRENCE_RULE_MAP } from "./constants" import { DUE_MAP, INITIAL_DUE, RECURRENCE_RULE_MAP } from "./constants"
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
@@ -84,6 +85,20 @@ export function getCompletionsForToday({
return getCompletionsForDate({ habit, date: getTodayInTimezone(timezone), timezone }) return getCompletionsForDate({ habit, date: getTodayInTimezone(timezone), timezone })
} }
export function getTodayCompletions({ selectedHabitId }: PomodoroAtom, { habits }: HabitsData, { system: { timezone } }: Settings): number {
if (!selectedHabitId)
return 0;
const selectedHabit = habits.find(h => h.id === selectedHabitId!);
if (!selectedHabit)
return 0;
return getCompletionsForToday({
habit: selectedHabit,
timezone: timezone
});
}
export function getCompletedHabitsForDate({ export function getCompletedHabitsForDate({
habits, habits,
date, date,