diff --git a/CHANGELOG.md b/CHANGELOG.md index ec1b98a..74b1cd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Version 0.2.2 + +### Changed + +* persist "show all" settings in browser (#72) + +### Fixed + +* nav bar spacing +* completion count badge + ## Version 0.2.1 ### Changed diff --git a/app/calendar/page.tsx b/app/calendar/page.tsx index 7f39efd..8993563 100644 --- a/app/calendar/page.tsx +++ b/app/calendar/page.tsx @@ -1,6 +1,7 @@ import Layout from '@/components/Layout' import HabitCalendar from '@/components/HabitCalendar' import { ViewToggle } from '@/components/ViewToggle' +import CompletionCountBadge from '@/components/CompletionCountBadge' export default function CalendarPage() { return ( diff --git a/app/debug/habits/page.tsx b/app/debug/habits/page.tsx new file mode 100644 index 0000000..9ae7e84 --- /dev/null +++ b/app/debug/habits/page.tsx @@ -0,0 +1,70 @@ +'use client' + +import { useHabits } from "@/hooks/useHabits"; +import { habitsAtom, settingsAtom } from "@/lib/atoms"; +import { Habit } from "@/lib/types"; +import { useAtom } from "jotai"; +import { DateTime } from "luxon"; + + + +type CompletionCache = { + [dateKey: string]: { // dateKey format: "YYYY-MM-DD" + [habitId: string]: number // number of completions on that date + } +} + + +export default function DebugPage() { + const [habits] = useAtom(habitsAtom); + const [settings] = useAtom(settingsAtom); + + function buildCompletionCache(habits: Habit[], timezone: string): CompletionCache { + const cache: CompletionCache = {}; + + habits.forEach(habit => { + habit.completions.forEach(utcTimestamp => { + // Convert UTC timestamp to local date string in specified timezone + const localDate = DateTime + .fromISO(utcTimestamp) + .setZone(timezone) + .toFormat('yyyy-MM-dd'); + + if (!cache[localDate]) { + cache[localDate] = {}; + } + + // Increment completion count for this habit on this date + cache[localDate][habit.id] = (cache[localDate][habit.id] || 0) + 1; + }); + }); + + return cache; + } + + function getCompletedHabitsForDate( + habits: Habit[], + date: DateTime, + timezone: string, + completionCache: CompletionCache + ): Habit[] { + const dateKey = date.setZone(timezone).toFormat('yyyy-MM-dd'); + const dateCompletions = completionCache[dateKey] || {}; + + return habits.filter(habit => { + const completionsNeeded = habit.targetCompletions || 1; + const completionsAchieved = dateCompletions[habit.id] || 0; + return completionsAchieved >= completionsNeeded; + }); + } + + const habitCache = buildCompletionCache(habits.habits, settings.system.timezone); + + return ( +
+

Debug Page

+
+
+
+ ); +} \ No newline at end of file diff --git a/components/CompletionCountBadge.tsx b/components/CompletionCountBadge.tsx index 569e35a..688d1e4 100644 --- a/components/CompletionCountBadge.tsx +++ b/components/CompletionCountBadge.tsx @@ -1,40 +1,35 @@ -import { Badge } from '@/components/ui/badge' -import { Habit } from '@/lib/types' -import { isHabitDue, getCompletionsForDate } from '@/lib/utils' +import { Badge } from "@/components/ui/badge" +import { useAtom } from 'jotai' +import { completedHabitsMapAtom, habitsAtom, habitsByDateFamily } from '@/lib/atoms' +import { getTodayInTimezone } from '@/lib/utils' +import { useHabits } from '@/hooks/useHabits' +import { settingsAtom } from '@/lib/atoms' interface CompletionCountBadgeProps { - habits: Habit[] - selectedDate: luxon.DateTime - timezone: string - type: 'tasks' | 'habits' + type: 'habits' | 'tasks' + date?: string } -export function CompletionCountBadge({ habits, selectedDate, timezone, type }: CompletionCountBadgeProps) { - const filteredHabits = habits.filter(habit => { - const isTask = type === 'tasks' - if ((habit.isTask === isTask) && isHabitDue({ - habit, - timezone, - date: selectedDate - })) { - const completions = getCompletionsForDate({ habit, date: selectedDate, timezone }) - return completions >= (habit.targetCompletions || 1) - } - return false - }).length +export default function CompletionCountBadge({ + type, + date +}: CompletionCountBadgeProps) { + const [settings] = useAtom(settingsAtom) + const [completedHabitsMap] = useAtom(completedHabitsMapAtom) + const targetDate = date || getTodayInTimezone(settings.system.timezone) + const [dueHabits] = useAtom(habitsByDateFamily(targetDate)) - const totalHabits = habits.filter(habit => - (habit.isTask === (type === 'tasks')) && - isHabitDue({ - habit, - timezone, - date: selectedDate - }) + const completedCount = completedHabitsMap.get(targetDate)?.filter(h => + type === 'tasks' ? h.isTask : !h.isTask + ).length || 0 + + const totalCount = dueHabits.filter(h => + type === 'tasks' ? h.isTask : !h.isTask ).length return ( - {`${filteredHabits}/${totalHabits} Completed`} + {`${completedCount}/${totalCount} Completed`} ) } diff --git a/components/DailyOverview.tsx b/components/DailyOverview.tsx index b8bef36..4005a40 100644 --- a/components/DailyOverview.tsx +++ b/components/DailyOverview.tsx @@ -1,4 +1,5 @@ import { Circle, Coins, ArrowRight, CircleCheck, ChevronDown, ChevronUp, Timer, Plus } from 'lucide-react' +import CompletionCountBadge from './CompletionCountBadge' import { ContextMenu, ContextMenuContent, @@ -9,7 +10,7 @@ import { cn, isHabitDueToday, getHabitFreq } from '@/lib/utils' import Link from 'next/link' import { useState, useEffect } from 'react' import { useAtom } from 'jotai' -import { pomodoroAtom, settingsAtom, completedHabitsMapAtom, browserSettingsAtom, BrowserSettings, hasTasksAtom } from '@/lib/atoms' +import { pomodoroAtom, settingsAtom, completedHabitsMapAtom, browserSettingsAtom, BrowserSettings, hasTasksAtom, dailyHabitsAtom } from '@/lib/atoms' import { getTodayInTimezone, isSameDate, t2d, d2t, getNow } from '@/lib/utils' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' @@ -34,29 +35,15 @@ export default function DailyOverview({ }: UpcomingItemsProps) { const { completeHabit, undoComplete } = useHabits() const [settings] = useAtom(settingsAtom) - const [dailyHabits, setDailyHabits] = useState([]) - const [dailyTasks, setDailyTasks] = useState([]) const [completedHabitsMap] = useAtom(completedHabitsMapAtom) + const [dailyItems] = useAtom(dailyHabitsAtom) + const dailyTasks = dailyItems.filter(habit => habit.isTask) + const dailyHabits = dailyItems.filter(habit => !habit.isTask) const today = getTodayInTimezone(settings.system.timezone) const todayCompletions = completedHabitsMap.get(today) || [] const { saveHabit } = useHabits() const [browserSettings, setBrowserSettings] = useAtom(browserSettingsAtom) - useEffect(() => { - // Filter habits and tasks that are due today and not archived - const filteredHabits = habits.filter(habit => - !habit.isTask && - !habit.archived && - isHabitDueToday({ habit, timezone: settings.system.timezone }) - ) - const filteredTasks = habits.filter(habit => - habit.isTask && - isHabitDueToday({ habit, timezone: settings.system.timezone }) - ) - setDailyHabits(filteredHabits) - setDailyTasks(filteredTasks) - }, [habits]) - // Get all wishlist items sorted by redeemable status (non-redeemable first) then by coin cost // Filter out archived wishlist items const sortedWishlistItems = wishlistItems @@ -74,9 +61,6 @@ export default function DailyOverview({ return a.coinCost - b.coinCost }) - const [expandedHabits, setExpandedHabits] = useState(false) - const [expandedTasks, setExpandedTasks] = useState(false) - const [expandedWishlist, setExpandedWishlist] = useState(false) const [hasTasks] = useAtom(hasTasksAtom) const [_, setPomo] = useAtom(pomodoroAtom) const [modalConfig, setModalConfig] = useState<{ @@ -126,13 +110,7 @@ export default function DailyOverview({

Daily Tasks

- - {`${dailyTasks.filter(task => { - const completions = (completedHabitsMap.get(today) || []) - .filter(h => h.id === task.id).length; - return completions >= (task.targetCompletions || 1); - }).length}/${dailyTasks.length} Completed`} - +
-