diff --git a/habits.db b/habits.db index afe2caa..ad7d8ff 100644 Binary files a/habits.db and b/habits.db differ diff --git a/src/components/Calendar.tsx b/src/components/Calendar.tsx index 85e9b48..1c236ea 100644 --- a/src/components/Calendar.tsx +++ b/src/components/Calendar.tsx @@ -19,6 +19,12 @@ export function Calendar({ }: CalendarProps) { const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + const getFirstDayOfMonth = (year: number, month: number) => { + const date = new Date(year, month, 1); + // Convert Sunday (0) to 6 for our Monday-based week + return date.getDay() === 0 ? 6 : date.getDay() - 1; + }; + return (
@@ -47,69 +53,116 @@ export function Calendar({ {day}
))} - {Array.from({ length: getDaysInMonth(currentMonth.getFullYear(), currentMonth.getMonth()) }).map((_, index) => { - const date = new Date( - currentMonth.getFullYear(), - currentMonth.getMonth(), - index + 1 - ).toISOString().split('T')[0]; - const completedHabits = getCompletedHabitsForDate(date); - const incompleteHabits = habits.filter(habit => !habit.completedDates.includes(date)); + + {(() => { + const year = currentMonth.getFullYear(); + const month = currentMonth.getMonth(); + const firstDayOfMonth = getFirstDayOfMonth(year, month); + const daysInMonth = getDaysInMonth(year, month); + const daysInPrevMonth = getDaysInMonth(year, month - 1); - return ( -
- {index + 1} - {habits.length > 0 && ( -
-
-
0 - ? 'bg-green-500 shadow-sm shadow-green-200' - : 'bg-gray-300 dark:bg-gray-600' - } rounded-full transition-colors duration-200`} - /> -
-
- {completedHabits.length > 0 && ( -
- - ✓ Completed - -
    - {completedHabits.map(habit => ( -
  • - {habit.name} -
  • - ))} -
-
- )} - {incompleteHabits.length > 0 && ( -
- - ○ Pending - -
    - {incompleteHabits.map(habit => ( -
  • - {habit.name} -
  • - ))} -
-
- )} + // Calculate days to show + const days = []; + + // Previous month days + for (let i = 0; i < firstDayOfMonth; i++) { + const day = daysInPrevMonth - firstDayOfMonth + i + 1; + const date = new Date(year, month - 1, day).toISOString().split('T')[0]; + days.push({ + date, + dayNumber: day, + isCurrentMonth: false + }); + } + + // Current month days + for (let i = 1; i <= daysInMonth; i++) { + const date = new Date(year, month, i).toISOString().split('T')[0]; + days.push({ + date, + dayNumber: i, + isCurrentMonth: true + }); + } + + // Next month days to complete the grid + const remainingDays = 42 - days.length; // 6 rows * 7 days + for (let i = 1; i <= remainingDays; i++) { + const date = new Date(year, month + 1, i).toISOString().split('T')[0]; + days.push({ + date, + dayNumber: i, + isCurrentMonth: false + }); + } + + return days.map(({ date, dayNumber, isCurrentMonth }) => { + const completedHabits = getCompletedHabitsForDate(date); + const incompleteHabits = habits.filter(habit => !habit.completedDates.includes(date)); + + return ( +
+ + {dayNumber} + + {habits.length > 0 && ( +
+
+
0 + ? 'bg-green-500 shadow-sm shadow-green-200' + : 'bg-gray-300 dark:bg-gray-600' + } rounded-full transition-colors duration-200`} + /> +
+
+ {completedHabits.length > 0 && ( +
+ + ✓ Completed + +
    + {completedHabits.map(habit => ( +
  • + {habit.name} +
  • + ))} +
+
+ )} + {incompleteHabits.length > 0 && ( +
+ + ○ Pending + +
    + {incompleteHabits.map(habit => ( +
  • + {habit.name} +
  • + ))} +
+
+ )} +
-
- )} -
- ); - })} + )} +
+ ); + }); + })()}
); diff --git a/src/components/HabitList.tsx b/src/components/HabitList.tsx index b775b92..4c7adb3 100644 --- a/src/components/HabitList.tsx +++ b/src/components/HabitList.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Trash2 } from 'lucide-react'; import { Habit } from '../types'; @@ -13,58 +13,111 @@ interface HabitListProps { } const calculateStreak = (completedDates: string[]): { currentStreak: number; bestStreak: number } => { - if (completedDates.length === 0) return { currentStreak: 0, bestStreak: 0 }; - - const sortedDates = [...completedDates].sort((a, b) => - new Date(a).getTime() - new Date(b).getTime() - ); - - let currentStreak = 1; - let bestStreak = 1; - let tempStreak = 1; - - // Check if the last completion was today or yesterday - const lastDate = new Date(sortedDates[sortedDates.length - 1]); - const today = new Date(); - today.setHours(0, 0, 0, 0); - lastDate.setHours(0, 0, 0, 0); - - const diffDays = Math.floor((today.getTime() - lastDate.getTime()) / (24 * 60 * 60 * 1000)); - - // If the last completion was more than a day ago, current streak is 0 - if (diffDays > 1) { - currentStreak = 0; + if (!completedDates || completedDates.length === 0) { + return { currentStreak: 0, bestStreak: 0 }; } - // Calculate streaks - for (let i = 1; i < sortedDates.length; i++) { - const prevDate = new Date(sortedDates[i - 1]); - const currDate = new Date(sortedDates[i]); - prevDate.setHours(0, 0, 0, 0); - currDate.setHours(0, 0, 0, 0); - - const diffTime = currDate.getTime() - prevDate.getTime(); - const diffDays = Math.floor(diffTime / (24 * 60 * 60 * 1000)); - - if (diffDays === 1) { - tempStreak++; - bestStreak = Math.max(bestStreak, tempStreak); - } else if (diffDays === 0) { - // Same day completions don't affect streak - continue; - } else { - tempStreak = 1; + // Get today's date at midnight + const today = new Date(); + today.setHours(0, 0, 0, 0); + const todayStr = today.toISOString().split('T')[0]; + + // Sort dates in descending order (most recent first) + const sortedDates = [...completedDates] + .filter(date => !isNaN(new Date(date).getTime())) + .sort((a, b) => new Date(b).getTime() - new Date(a).getTime()); + + let currentStreak = 0; + let bestStreak = 0; + let tempStreak = 0; + + // First, check if today is completed + const hasTodayCompleted = sortedDates.includes(todayStr); + + if (!hasTodayCompleted) { + // If today isn't completed, current streak is 0 + currentStreak = 0; + } else { + // Start counting current streak from today + let checkDate = new Date(today); + currentStreak = 1; // Start with 1 for today + + // Check previous days + while (true) { + // Move to previous day + checkDate.setDate(checkDate.getDate() - 1); + const dateStr = checkDate.toISOString().split('T')[0]; + + if (sortedDates.includes(dateStr)) { + currentStreak++; + } else { + break; // Break streak if a day is missed + } } } - // Current streak should be the same as tempStreak if the last completion was today or yesterday - if (diffDays <= 1) { - currentStreak = tempStreak; + // Calculate best streak + for (let i = 0; i < sortedDates.length; i++) { + const currentDate = new Date(sortedDates[i]); + + if (i === 0) { + tempStreak = 1; + } else { + const prevDate = new Date(sortedDates[i - 1]); + const diffDays = Math.floor( + (prevDate.getTime() - currentDate.getTime()) / (1000 * 60 * 60 * 24) + ); + + if (diffDays === 1) { + tempStreak++; + } else if (diffDays === 0) { + // Same day, skip + continue; + } else { + // Reset streak on gap + bestStreak = Math.max(bestStreak, tempStreak); + tempStreak = 1; + } + } } + + // Final check for best streak + bestStreak = Math.max(bestStreak, tempStreak); + // Also check if current streak is the best + bestStreak = Math.max(bestStreak, currentStreak); return { currentStreak, bestStreak }; }; +const getCurrentWeekDates = () => { + // Start with Sunday (today's week) + const now = new Date(); + const sunday = new Date(now); + sunday.setDate(now.getDate() - now.getDay()); + + const weekDates = []; + for (let i = 0; i < 7; i++) { + const date = new Date(sunday); + date.setDate(sunday.getDate() + i); + // Format as YYYY-MM-DD + const formattedDate = date.toISOString().split('T')[0]; + weekDates.push(formattedDate); + } + + return weekDates; +}; + +// If you want Monday first, rotate the array +const currentWeek = (() => { + const dates = getCurrentWeekDates(); + // Move Sunday to the end + const sunday = dates.shift()!; + dates.push(sunday); + return dates; +})(); + +const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + export function HabitList({ habits, currentWeek, @@ -74,25 +127,35 @@ export function HabitList({ onDeleteHabit, onUpdateStreak, }: HabitListProps) { + useEffect(() => { + console.log('Current week dates:', + currentWeek.map(date => + `${new Date(date).toLocaleDateString()} (${daysOfWeek[new Date(date).getDay() === 0 ? 6 : new Date(date).getDay() - 1]})` + ) + ); + }, []); + return ( - {daysOfWeek.map((day, index) => ( - - ))} - - + {currentWeek.map((dateStr, index) => { + const date = new Date(dateStr); + // Ensure date is interpreted in local timezone + const displayDate = new Date(date.getTime() + date.getTimezoneOffset() * 60000); + + return ( + + ); + })} + + @@ -130,12 +193,12 @@ export function HabitList({ ))}
Habit - {day} -
- {new Date(currentWeek[index]).getDate()} -
-
- Current Streak - - Best Streak - +
{daysOfWeek[index]}
+
+ {displayDate.getDate()} +
+
Current StreakBest Streak Actions
- {calculateStreak(habit.completedDates).currentStreak} + {calculateStreak(habit.completedDates || []).currentStreak} - {calculateStreak(habit.completedDates).bestStreak} + {calculateStreak(habit.completedDates || []).bestStreak}