-
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 (
| Habit |
- {daysOfWeek.map((day, index) => (
-
- {day}
-
- {new Date(currentWeek[index]).getDate()}
-
- |
- ))}
-
- Current Streak
- |
-
- Best Streak
- |
+ {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 (
+
+ {daysOfWeek[index]}
+
+ {displayDate.getDate()}
+
+ |
+ );
+ })}
+ Current Streak |
+ Best Streak |
Actions |
@@ -130,12 +193,12 @@ export function HabitList({
))}
- {calculateStreak(habit.completedDates).currentStreak}
+ {calculateStreak(habit.completedDates || []).currentStreak}
|
- {calculateStreak(habit.completedDates).bestStreak}
+ {calculateStreak(habit.completedDates || []).bestStreak}
|
|