Skip to content

Commit dea2b30

Browse files
committed
fix completion badge
1 parent ea0203d commit dea2b30

12 files changed

+231
-135
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## Version 0.2.2
4+
5+
### Changed
6+
7+
* persist "show all" settings in browser (#72)
8+
9+
### Fixed
10+
11+
* nav bar spacing
12+
* completion count badge
13+
314
## Version 0.2.1
415

516
### Changed

app/calendar/page.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Layout from '@/components/Layout'
22
import HabitCalendar from '@/components/HabitCalendar'
33
import { ViewToggle } from '@/components/ViewToggle'
4+
import CompletionCountBadge from '@/components/CompletionCountBadge'
45

56
export default function CalendarPage() {
67
return (

app/debug/habits/page.tsx

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use client'
2+
3+
import { useHabits } from "@/hooks/useHabits";
4+
import { habitsAtom, settingsAtom } from "@/lib/atoms";
5+
import { Habit } from "@/lib/types";
6+
import { useAtom } from "jotai";
7+
import { DateTime } from "luxon";
8+
9+
10+
11+
type CompletionCache = {
12+
[dateKey: string]: { // dateKey format: "YYYY-MM-DD"
13+
[habitId: string]: number // number of completions on that date
14+
}
15+
}
16+
17+
18+
export default function DebugPage() {
19+
const [habits] = useAtom(habitsAtom);
20+
const [settings] = useAtom(settingsAtom);
21+
22+
function buildCompletionCache(habits: Habit[], timezone: string): CompletionCache {
23+
const cache: CompletionCache = {};
24+
25+
habits.forEach(habit => {
26+
habit.completions.forEach(utcTimestamp => {
27+
// Convert UTC timestamp to local date string in specified timezone
28+
const localDate = DateTime
29+
.fromISO(utcTimestamp)
30+
.setZone(timezone)
31+
.toFormat('yyyy-MM-dd');
32+
33+
if (!cache[localDate]) {
34+
cache[localDate] = {};
35+
}
36+
37+
// Increment completion count for this habit on this date
38+
cache[localDate][habit.id] = (cache[localDate][habit.id] || 0) + 1;
39+
});
40+
});
41+
42+
return cache;
43+
}
44+
45+
function getCompletedHabitsForDate(
46+
habits: Habit[],
47+
date: DateTime,
48+
timezone: string,
49+
completionCache: CompletionCache
50+
): Habit[] {
51+
const dateKey = date.setZone(timezone).toFormat('yyyy-MM-dd');
52+
const dateCompletions = completionCache[dateKey] || {};
53+
54+
return habits.filter(habit => {
55+
const completionsNeeded = habit.targetCompletions || 1;
56+
const completionsAchieved = dateCompletions[habit.id] || 0;
57+
return completionsAchieved >= completionsNeeded;
58+
});
59+
}
60+
61+
const habitCache = buildCompletionCache(habits.habits, settings.system.timezone);
62+
63+
return (
64+
<div className="p-4">
65+
<h1 className="text-xl font-bold mb-4">Debug Page</h1>
66+
<div className="bg-gray-100 p-4 rounded break-all">
67+
</div>
68+
</div>
69+
);
70+
}

components/CompletionCountBadge.tsx

+23-28
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,35 @@
1-
import { Badge } from '@/components/ui/badge'
2-
import { Habit } from '@/lib/types'
3-
import { isHabitDue, getCompletionsForDate } from '@/lib/utils'
1+
import { Badge } from "@/components/ui/badge"
2+
import { useAtom } from 'jotai'
3+
import { completedHabitsMapAtom, habitsAtom, habitsByDateFamily } from '@/lib/atoms'
4+
import { getTodayInTimezone } from '@/lib/utils'
5+
import { useHabits } from '@/hooks/useHabits'
6+
import { settingsAtom } from '@/lib/atoms'
47

58
interface CompletionCountBadgeProps {
6-
habits: Habit[]
7-
selectedDate: luxon.DateTime
8-
timezone: string
9-
type: 'tasks' | 'habits'
9+
type: 'habits' | 'tasks'
10+
date?: string
1011
}
1112

12-
export function CompletionCountBadge({ habits, selectedDate, timezone, type }: CompletionCountBadgeProps) {
13-
const filteredHabits = habits.filter(habit => {
14-
const isTask = type === 'tasks'
15-
if ((habit.isTask === isTask) && isHabitDue({
16-
habit,
17-
timezone,
18-
date: selectedDate
19-
})) {
20-
const completions = getCompletionsForDate({ habit, date: selectedDate, timezone })
21-
return completions >= (habit.targetCompletions || 1)
22-
}
23-
return false
24-
}).length
13+
export default function CompletionCountBadge({
14+
type,
15+
date
16+
}: CompletionCountBadgeProps) {
17+
const [settings] = useAtom(settingsAtom)
18+
const [completedHabitsMap] = useAtom(completedHabitsMapAtom)
19+
const targetDate = date || getTodayInTimezone(settings.system.timezone)
20+
const [dueHabits] = useAtom(habitsByDateFamily(targetDate))
2521

26-
const totalHabits = habits.filter(habit =>
27-
(habit.isTask === (type === 'tasks')) &&
28-
isHabitDue({
29-
habit,
30-
timezone,
31-
date: selectedDate
32-
})
22+
const completedCount = completedHabitsMap.get(targetDate)?.filter(h =>
23+
type === 'tasks' ? h.isTask : !h.isTask
24+
).length || 0
25+
26+
const totalCount = dueHabits.filter(h =>
27+
type === 'tasks' ? h.isTask : !h.isTask
3328
).length
3429

3530
return (
3631
<Badge variant="secondary">
37-
{`${filteredHabits}/${totalHabits} Completed`}
32+
{`${completedCount}/${totalCount} Completed`}
3833
</Badge>
3934
)
4035
}

components/DailyOverview.tsx

+19-47
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Circle, Coins, ArrowRight, CircleCheck, ChevronDown, ChevronUp, Timer, Plus } from 'lucide-react'
2+
import CompletionCountBadge from './CompletionCountBadge'
23
import {
34
ContextMenu,
45
ContextMenuContent,
@@ -9,7 +10,7 @@ import { cn, isHabitDueToday, getHabitFreq } from '@/lib/utils'
910
import Link from 'next/link'
1011
import { useState, useEffect } from 'react'
1112
import { useAtom } from 'jotai'
12-
import { pomodoroAtom, settingsAtom, completedHabitsMapAtom, browserSettingsAtom, BrowserSettings, hasTasksAtom } from '@/lib/atoms'
13+
import { pomodoroAtom, settingsAtom, completedHabitsMapAtom, browserSettingsAtom, BrowserSettings, hasTasksAtom, dailyHabitsAtom } from '@/lib/atoms'
1314
import { getTodayInTimezone, isSameDate, t2d, d2t, getNow } from '@/lib/utils'
1415
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
1516
import { Badge } from '@/components/ui/badge'
@@ -34,29 +35,15 @@ export default function DailyOverview({
3435
}: UpcomingItemsProps) {
3536
const { completeHabit, undoComplete } = useHabits()
3637
const [settings] = useAtom(settingsAtom)
37-
const [dailyHabits, setDailyHabits] = useState<Habit[]>([])
38-
const [dailyTasks, setDailyTasks] = useState<Habit[]>([])
3938
const [completedHabitsMap] = useAtom(completedHabitsMapAtom)
39+
const [dailyItems] = useAtom(dailyHabitsAtom)
40+
const dailyTasks = dailyItems.filter(habit => habit.isTask)
41+
const dailyHabits = dailyItems.filter(habit => !habit.isTask)
4042
const today = getTodayInTimezone(settings.system.timezone)
4143
const todayCompletions = completedHabitsMap.get(today) || []
4244
const { saveHabit } = useHabits()
4345
const [browserSettings, setBrowserSettings] = useAtom(browserSettingsAtom)
4446

45-
useEffect(() => {
46-
// Filter habits and tasks that are due today and not archived
47-
const filteredHabits = habits.filter(habit =>
48-
!habit.isTask &&
49-
!habit.archived &&
50-
isHabitDueToday({ habit, timezone: settings.system.timezone })
51-
)
52-
const filteredTasks = habits.filter(habit =>
53-
habit.isTask &&
54-
isHabitDueToday({ habit, timezone: settings.system.timezone })
55-
)
56-
setDailyHabits(filteredHabits)
57-
setDailyTasks(filteredTasks)
58-
}, [habits])
59-
6047
// Get all wishlist items sorted by redeemable status (non-redeemable first) then by coin cost
6148
// Filter out archived wishlist items
6249
const sortedWishlistItems = wishlistItems
@@ -74,9 +61,6 @@ export default function DailyOverview({
7461
return a.coinCost - b.coinCost
7562
})
7663

77-
const [expandedHabits, setExpandedHabits] = useState(false)
78-
const [expandedTasks, setExpandedTasks] = useState(false)
79-
const [expandedWishlist, setExpandedWishlist] = useState(false)
8064
const [hasTasks] = useAtom(hasTasksAtom)
8165
const [_, setPomo] = useAtom(pomodoroAtom)
8266
const [modalConfig, setModalConfig] = useState<{
@@ -126,13 +110,7 @@ export default function DailyOverview({
126110
<h3 className="font-semibold">Daily Tasks</h3>
127111
</div>
128112
<div className="flex items-center gap-2">
129-
<Badge variant="secondary">
130-
{`${dailyTasks.filter(task => {
131-
const completions = (completedHabitsMap.get(today) || [])
132-
.filter(h => h.id === task.id).length;
133-
return completions >= (task.targetCompletions || 1);
134-
}).length}/${dailyTasks.length} Completed`}
135-
</Badge>
113+
<CompletionCountBadge type="tasks" />
136114
<Button
137115
variant="ghost"
138116
size="sm"
@@ -149,7 +127,7 @@ export default function DailyOverview({
149127
</Button>
150128
</div>
151129
</div>
152-
<ul className={`grid gap-2 transition-all duration-300 ease-in-out ${expandedTasks ? 'max-h-none' : 'max-h-[200px]'} overflow-hidden`}>
130+
<ul className={`grid gap-2 transition-all duration-300 ease-in-out ${browserSettings.expandedTasks ? 'max-h-none' : 'max-h-[200px]'} overflow-hidden`}>
153131
{dailyTasks
154132
.sort((a, b) => {
155133
// First by completion status
@@ -177,7 +155,7 @@ export default function DailyOverview({
177155
const bTarget = b.targetCompletions || 1;
178156
return bTarget - aTarget;
179157
})
180-
.slice(0, expandedTasks ? undefined : 5)
158+
.slice(0, browserSettings.expandedTasks ? undefined : 5)
181159
.map((habit) => {
182160
const completionsToday = habit.completions.filter(completion =>
183161
isSameDate(t2d({ timestamp: completion, timezone: settings.system.timezone }), t2d({ timestamp: d2t({ dateTime: getNow({ timezone: settings.system.timezone }) }), timezone: settings.system.timezone }))
@@ -279,10 +257,10 @@ export default function DailyOverview({
279257
</ul>
280258
<div className="flex items-center justify-between">
281259
<button
282-
onClick={() => setExpandedTasks(!expandedTasks)}
260+
onClick={() => setBrowserSettings(prev => ({ ...prev, expandedTasks: !prev.expandedTasks }))}
283261
className="text-sm text-muted-foreground hover:text-primary flex items-center gap-1"
284262
>
285-
{expandedTasks ? (
263+
{browserSettings.expandedTasks ? (
286264
<>
287265
Show less
288266
<ChevronUp className="h-3 w-3" />
@@ -337,13 +315,7 @@ export default function DailyOverview({
337315
<h3 className="font-semibold">Daily Habits</h3>
338316
</div>
339317
<div className="flex items-center gap-2">
340-
<Badge variant="secondary">
341-
{`${dailyHabits.filter(habit => {
342-
const completions = (completedHabitsMap.get(today) || [])
343-
.filter(h => h.id === habit.id).length;
344-
return completions >= (habit.targetCompletions || 1);
345-
}).length}/${dailyHabits.length} Completed`}
346-
</Badge>
318+
<CompletionCountBadge type="habits" />
347319
<Button
348320
variant="ghost"
349321
size="sm"
@@ -360,7 +332,7 @@ export default function DailyOverview({
360332
</Button>
361333
</div>
362334
</div>
363-
<ul className={`grid gap-2 transition-all duration-300 ease-in-out ${expandedHabits ? 'max-h-none' : 'max-h-[200px]'} overflow-hidden`}>
335+
<ul className={`grid gap-2 transition-all duration-300 ease-in-out ${browserSettings.expandedHabits ? 'max-h-none' : 'max-h-[200px]'} overflow-hidden`}>
364336
{dailyHabits
365337
.sort((a, b) => {
366338
// First by completion status
@@ -388,7 +360,7 @@ export default function DailyOverview({
388360
const bTarget = b.targetCompletions || 1;
389361
return bTarget - aTarget;
390362
})
391-
.slice(0, expandedHabits ? undefined : 5)
363+
.slice(0, browserSettings.expandedHabits ? undefined : 5)
392364
.map((habit) => {
393365
const completionsToday = habit.completions.filter(completion =>
394366
isSameDate(t2d({ timestamp: completion, timezone: settings.system.timezone }), t2d({ timestamp: d2t({ dateTime: getNow({ timezone: settings.system.timezone }) }), timezone: settings.system.timezone }))
@@ -490,10 +462,10 @@ export default function DailyOverview({
490462
</ul>
491463
<div className="flex items-center justify-between">
492464
<button
493-
onClick={() => setExpandedHabits(!expandedHabits)}
465+
onClick={() => setBrowserSettings(prev => ({ ...prev, expandedHabits: !prev.expandedHabits }))}
494466
className="text-sm text-muted-foreground hover:text-primary flex items-center gap-1"
495467
>
496-
{expandedHabits ? (
468+
{browserSettings.expandedHabits ? (
497469
<>
498470
Show less
499471
<ChevronUp className="h-3 w-3" />
@@ -525,15 +497,15 @@ export default function DailyOverview({
525497
</Badge>
526498
</div>
527499
<div>
528-
<div className={`space-y-3 transition-all duration-300 ease-in-out ${expandedWishlist ? 'max-h-none' : 'max-h-[200px]'} overflow-hidden`}>
500+
<div className={`space-y-3 transition-all duration-300 ease-in-out ${browserSettings.expandedWishlist ? 'max-h-none' : 'max-h-[200px]'} overflow-hidden`}>
529501
{sortedWishlistItems.length === 0 ? (
530502
<div className="text-center text-muted-foreground text-sm py-4">
531503
No wishlist items yet. Add some goals to work towards!
532504
</div>
533505
) : (
534506
<>
535507
{sortedWishlistItems
536-
.slice(0, expandedWishlist ? undefined : 5)
508+
.slice(0, browserSettings.expandedWishlist ? undefined : 5)
537509
.map((item) => {
538510
const isRedeemable = item.coinCost <= coinBalance
539511
return (
@@ -587,10 +559,10 @@ export default function DailyOverview({
587559
</div>
588560
<div className="flex items-center justify-between">
589561
<button
590-
onClick={() => setExpandedWishlist(!expandedWishlist)}
562+
onClick={() => setBrowserSettings(prev => ({ ...prev, expandedWishlist: !prev.expandedWishlist }))}
591563
className="text-sm text-muted-foreground hover:text-primary flex items-center gap-1"
592564
>
593-
{expandedWishlist ? (
565+
{browserSettings.expandedWishlist ? (
594566
<>
595567
Show less
596568
<ChevronUp className="h-3 w-3" />

0 commit comments

Comments
 (0)