added little widget on calendar page of mobile version which shows habits on selected day

This commit is contained in:
Harivansh Rathi 2024-11-23 15:39:24 -05:00
parent 9e1602723d
commit 0186c1f730
5 changed files with 271 additions and 107 deletions

View file

@ -39,7 +39,7 @@ export const Calendar: React.FC<CalendarProps> = ({
}) => { }) => {
const { theme } = useThemeContext(); const { theme } = useThemeContext();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const getFirstDayOfMonth = (year: number, month: number) => { const getFirstDayOfMonth = (year: number, month: number) => {
@ -81,7 +81,7 @@ export const Calendar: React.FC<CalendarProps> = ({
if (hideTimeoutRef.current) { if (hideTimeoutRef.current) {
clearTimeout(hideTimeoutRef.current); clearTimeout(hideTimeoutRef.current);
} }
const rect = e.currentTarget.getBoundingClientRect(); const rect = e.currentTarget.getBoundingClientRect();
setTooltipData({ setTooltipData({
x: rect.left + rect.width / 2, x: rect.left + rect.width / 2,
@ -118,10 +118,49 @@ export const Calendar: React.FC<CalendarProps> = ({
}; };
}, []); }, []);
// Update selected date data when habits change
const [selectedDate, setSelectedDate] = React.useState<{
date: string;
completedHabits: Habit[];
incompleteHabits: Habit[];
} | null>(null);
// Update the selected date data whenever habits change or a habit is toggled
React.useEffect(() => {
if (selectedDate) {
setSelectedDate({
date: selectedDate.date,
completedHabits: getCompletedHabitsForDate(selectedDate.date),
incompleteHabits: habits.filter(habit =>
!getCompletedHabitsForDate(selectedDate.date)
.map(h => h.id)
.includes(habit.id)
)
});
}
}, [habits, getCompletedHabitsForDate]);
// Modified habit toggle handler for mobile view
const handleMobileHabitToggle = async (e: React.MouseEvent, habitId: number, date: string) => {
e.stopPropagation();
await onToggleHabit(habitId, date);
// Update the selected date data immediately after toggling
setSelectedDate({
date,
completedHabits: getCompletedHabitsForDate(date),
incompleteHabits: habits.filter(habit =>
!getCompletedHabitsForDate(date)
.map(h => h.id)
.includes(habit.id)
)
});
};
return ( return (
<> <>
<div className={` <div className={`
rounded-lg shadow-md p-6 md:p-6 rounded-lg shadow-md p-6 md:p-6
${theme.calendar.background} ${theme.calendar.background}
${isMobile ? 'p-2 mx-[-1rem]' : ''} ${isMobile ? 'p-2 mx-[-1rem]' : ''}
`}> `}>
@ -152,21 +191,21 @@ export const Calendar: React.FC<CalendarProps> = ({
<div className="grid grid-cols-7 gap-1 md:gap-4"> <div className="grid grid-cols-7 gap-1 md:gap-4">
{daysOfWeek.map(day => ( {daysOfWeek.map(day => (
<div key={day} className={` <div key={day} className={`
text-center font-semibold mb-1 md:mb-2 text-center font-semibold mb-1 md:mb-2
${theme.calendar.weekDay} ${theme.calendar.weekDay}
${isMobile ? 'text-xs' : ''} ${isMobile ? 'text-xs' : ''}
`}> `}>
{isMobile ? day.charAt(0) : day} {isMobile ? day.charAt(0) : day}
</div> </div>
))} ))}
{(() => { {(() => {
const year = currentMonth.getFullYear(); const year = currentMonth.getFullYear();
const month = currentMonth.getMonth(); const month = currentMonth.getMonth();
const firstDayOfMonth = getFirstDayOfMonth(year, month); const firstDayOfMonth = getFirstDayOfMonth(year, month);
const daysInMonth = getDaysInMonth(year, month); const daysInMonth = getDaysInMonth(year, month);
const daysInPrevMonth = getDaysInMonth(year, month - 1); const daysInPrevMonth = getDaysInMonth(year, month - 1);
const days = []; const days = [];
// Previous month days // Previous month days
@ -205,20 +244,29 @@ export const Calendar: React.FC<CalendarProps> = ({
const completedHabits = getCompletedHabitsForDate(date); const completedHabits = getCompletedHabitsForDate(date);
const incompleteHabits = habits.filter(habit => !habit.completedDates.includes(date)); const incompleteHabits = habits.filter(habit => !habit.completedDates.includes(date));
const isToday = date === todayStr; const isToday = date === todayStr;
return ( return (
<div <div
key={date} key={date}
onClick={() => {
if (isMobile) {
setSelectedDate({
date,
completedHabits,
incompleteHabits
});
}
}}
className={` className={`
border rounded-lg relative border rounded-lg relative
${theme.border} ${theme.border}
${isCurrentMonth ? theme.calendar.day.default : theme.calendar.day.otherMonth} ${isCurrentMonth ? theme.calendar.day.default : theme.calendar.day.otherMonth}
${isToday ? theme.calendar.day.today : ''} ${isToday ? theme.calendar.day.today : ''}
${isMobile ? 'p-1 min-h-[60px]' : 'p-3 min-h-[80px]'} ${isMobile ? 'p-1 min-h-[60px] active:bg-gray-100 dark:active:bg-gray-800' : 'p-3 min-h-[80px]'}
`} `}
> >
<span className={` <span className={`
font-medium font-medium
${isCurrentMonth ? theme.text : theme.calendar.day.otherMonth} ${isCurrentMonth ? theme.text : theme.calendar.day.otherMonth}
${isToday ? 'relative' : ''} ${isToday ? 'relative' : ''}
${isMobile ? 'text-sm' : ''} ${isMobile ? 'text-sm' : ''}
@ -230,7 +278,7 @@ export const Calendar: React.FC<CalendarProps> = ({
absolute bottom-1 left-1/2 transform -translate-x-1/2 absolute bottom-1 left-1/2 transform -translate-x-1/2
${isMobile ? 'w-full px-1' : ''} ${isMobile ? 'w-full px-1' : ''}
`}> `}>
<div <div
className="relative" className="relative"
{...(!isMobile ? { {...(!isMobile ? {
onMouseEnter: (e) => showTooltip(e, date, completedHabits, incompleteHabits), onMouseEnter: (e) => showTooltip(e, date, completedHabits, incompleteHabits),
@ -241,8 +289,8 @@ export const Calendar: React.FC<CalendarProps> = ({
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className={` <div className={`
text-xs font-medium px-1.5 py-0.5 rounded text-xs font-medium px-1.5 py-0.5 rounded
${completedHabits.length > 0 ${completedHabits.length > 0
? 'text-green-700 dark:text-green-300' ? 'text-green-700 dark:text-green-300'
: `${theme.text} opacity-75` : `${theme.text} opacity-75`
} }
`}> `}>
@ -257,15 +305,15 @@ export const Calendar: React.FC<CalendarProps> = ({
<div className={` <div className={`
h-6 px-2.5 rounded-full cursor-pointer h-6 px-2.5 rounded-full cursor-pointer
transition-all duration-200 flex items-center justify-center gap-1 transition-all duration-200 flex items-center justify-center gap-1
${completedHabits.length > 0 ${completedHabits.length > 0
? 'bg-green-100 dark:bg-green-900/30 shadow-[0_2px_10px] shadow-green-900/20 dark:shadow-green-100/20' ? 'bg-green-100 dark:bg-green-900/30 shadow-[0_2px_10px] shadow-green-900/20 dark:shadow-green-100/20'
: `bg-gray-100 dark:bg-gray-800 shadow-sm` : `bg-gray-100 dark:bg-gray-800 shadow-sm`
} }
`}> `}>
<span className={` <span className={`
text-xs font-medium text-xs font-medium
${completedHabits.length > 0 ${completedHabits.length > 0
? 'text-green-700 dark:text-green-300' ? 'text-green-700 dark:text-green-300'
: 'text-gray-600 dark:text-gray-400' : 'text-gray-600 dark:text-gray-400'
} }
`}> `}>
@ -284,7 +332,81 @@ export const Calendar: React.FC<CalendarProps> = ({
</div> </div>
</div> </div>
{/* Tooltip portal - only render on desktop */} {/* Mobile Selected Date Details */}
{isMobile && selectedDate && (
<div className={`
mt-6 p-4 rounded-lg
${theme.cardBackground}
border ${theme.border}
shadow-md
`}>
<div className="mb-4">
<h3 className={`text-lg font-medium ${theme.text}`}>
{new Date(selectedDate.date).toLocaleDateString('default', {
month: 'long',
day: 'numeric',
year: 'numeric'
})}
</h3>
</div>
{selectedDate.completedHabits.length > 0 && (
<div className="mb-4">
<span className="text-emerald-500 dark:text-emerald-400 font-medium block mb-2.5 text-sm">
Completed
</span>
<ul className="space-y-2">
{selectedDate.completedHabits.map(habit => (
<li
key={habit.id}
className={`${theme.text} text-sm truncate flex items-center justify-between group`}
>
<span className="truncate mr-2">{habit.name}</span>
<button
onClick={(e) => handleMobileHabitToggle(e, habit.id, selectedDate.date)}
className={`p-1.5 rounded-lg transition-colors ${theme.habitItem}`}
>
<Check className="h-4 w-4 text-emerald-500 dark:text-emerald-400" />
</button>
</li>
))}
</ul>
</div>
)}
{selectedDate.incompleteHabits.length > 0 && (
<div>
<span className="text-rose-500 dark:text-rose-400 font-medium block mb-2.5 text-sm">
Pending
</span>
<ul className="space-y-2">
{selectedDate.incompleteHabits.map(habit => (
<li
key={habit.id}
className={`${theme.text} text-sm truncate flex items-center justify-between group`}
>
<span className="truncate mr-2">{habit.name}</span>
<button
onClick={(e) => handleMobileHabitToggle(e, habit.id, selectedDate.date)}
className={`p-1.5 rounded-lg transition-colors ${theme.habitItem}`}
>
<Check className="h-4 w-4 text-gray-400 dark:text-gray-500 hover:text-emerald-500 dark:hover:text-emerald-400" />
</button>
</li>
))}
</ul>
</div>
)}
{selectedDate.completedHabits.length === 0 && selectedDate.incompleteHabits.length === 0 && (
<p className={`text-sm ${theme.mutedText} text-center py-4`}>
No habits tracked for this date
</p>
)}
</div>
)}
{/* Desktop Tooltip Portal - unchanged */}
{!isMobile && tooltipData && createPortal( {!isMobile && tooltipData && createPortal(
<div <div
ref={tooltipRef} ref={tooltipRef}
@ -293,8 +415,8 @@ export const Calendar: React.FC<CalendarProps> = ({
className={` className={`
fixed fixed
transition-all duration-150 ease-in-out transition-all duration-150 ease-in-out
${tooltipData.isVisible ${tooltipData.isVisible
? 'opacity-100 translate-y-0' ? 'opacity-100 translate-y-0'
: 'opacity-0 translate-y-1' : 'opacity-0 translate-y-1'
} }
`} `}
@ -306,7 +428,7 @@ export const Calendar: React.FC<CalendarProps> = ({
zIndex: 100, zIndex: 100,
}} }}
> >
<div <div
className={` className={`
rounded-2xl p-5 rounded-2xl p-5
w-[240px] w-[240px]
@ -321,7 +443,7 @@ export const Calendar: React.FC<CalendarProps> = ({
`} `}
> >
{/* Updated Arrow */} {/* Updated Arrow */}
<div <div
className={` className={`
absolute -bottom-[6px] left-1/2 -translate-x-1/2 absolute -bottom-[6px] left-1/2 -translate-x-1/2
w-3 h-3 rotate-45 w-3 h-3 rotate-45
@ -330,7 +452,7 @@ export const Calendar: React.FC<CalendarProps> = ({
border-t-0 border-l-0 border-t-0 border-l-0
`} `}
/> />
<div className="relative"> <div className="relative">
{tooltipData.completedHabits.length > 0 && ( {tooltipData.completedHabits.length > 0 && (
<div className="mb-4"> <div className="mb-4">
@ -339,8 +461,8 @@ export const Calendar: React.FC<CalendarProps> = ({
</span> </span>
<ul className="space-y-2"> <ul className="space-y-2">
{tooltipData.completedHabits.map(habit => ( {tooltipData.completedHabits.map(habit => (
<li <li
key={habit.id} key={habit.id}
className={`${theme.text} text-sm truncate flex items-center justify-between group`} className={`${theme.text} text-sm truncate flex items-center justify-between group`}
> >
<span className="truncate mr-2">{habit.name}</span> <span className="truncate mr-2">{habit.name}</span>
@ -362,8 +484,8 @@ export const Calendar: React.FC<CalendarProps> = ({
</span> </span>
<ul className="space-y-2"> <ul className="space-y-2">
{tooltipData.incompleteHabits.map(habit => ( {tooltipData.incompleteHabits.map(habit => (
<li <li
key={habit.id} key={habit.id}
className={`${theme.text} text-sm truncate flex items-center justify-between group`} className={`${theme.text} text-sm truncate flex items-center justify-between group`}
> >
<span className="truncate mr-2">{habit.name}</span> <span className="truncate mr-2">{habit.name}</span>
@ -385,4 +507,4 @@ export const Calendar: React.FC<CalendarProps> = ({
)} )}
</> </>
); );
}; };

View file

@ -60,7 +60,7 @@ export function HabitList({
<tbody> <tbody>
{sortedHabits.map((habit) => { {sortedHabits.map((habit) => {
const { currentStreak } = calculateStreak(habit.completedDates); const { currentStreak } = calculateStreak(habit.completedDates);
return ( return (
<tr key={habit.id} className="border-t dark:border-gray-700"> <tr key={habit.id} className="border-t dark:border-gray-700">
<td className="px-4 py-2 dark:text-white"> <td className="px-4 py-2 dark:text-white">

View file

@ -36,7 +36,7 @@ export function SignUp({ onSwitchToLogin }: { onSwitchToLogin: () => void }) {
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
// Validate password before submission // Validate password before submission
const passwordValidationError = validatePassword(password); const passwordValidationError = validatePassword(password);
if (passwordValidationError) { if (passwordValidationError) {
@ -48,8 +48,12 @@ export function SignUp({ onSwitchToLogin }: { onSwitchToLogin: () => void }) {
setError(''); setError('');
setPasswordError(null); setPasswordError(null);
await signUp(email, password); await signUp(email, password);
} catch (err) { } catch (err: unknown) {
setError('Failed to create an account'); // Handle the error message
const errorMessage = err instanceof Error
? err.message
: 'Failed to create an account';
setError(errorMessage);
} }
}; };
@ -68,16 +72,16 @@ export function SignUp({ onSwitchToLogin }: { onSwitchToLogin: () => void }) {
<div className="min-h-screen w-full flex"> <div className="min-h-screen w-full flex">
<div className="hidden md:flex md:w-1/2 bg-black p-12 flex-col justify-center relative overflow-hidden"> <div className="hidden md:flex md:w-1/2 bg-black p-12 flex-col justify-center relative overflow-hidden">
<div className="absolute inset-0 flex items-center justify-center opacity-20"> <div className="absolute inset-0 flex items-center justify-center opacity-20">
<motion.div <motion.div
className="absolute w-96 h-96" className="absolute w-96 h-96"
animate={{ animate={{
rotate: 360, rotate: 360,
scale: [1, 1.2, 1], scale: [1, 1.2, 1],
}} }}
transition={{ transition={{
duration: 15, duration: 15,
repeat: Infinity, repeat: Infinity,
ease: "linear" ease: "linear"
}} }}
> >
<Circle className="w-full h-full text-white" /> <Circle className="w-full h-full text-white" />
@ -103,30 +107,30 @@ export function SignUp({ onSwitchToLogin }: { onSwitchToLogin: () => void }) {
</div> </div>
<div className="absolute inset-0 md:hidden overflow-hidden"> <div className="absolute inset-0 md:hidden overflow-hidden">
<motion.div <motion.div
className="absolute top-[-30%] right-[-20%] w-[80%] h-[80%] opacity-[0.07]" className="absolute top-[-30%] right-[-20%] w-[80%] h-[80%] opacity-[0.07]"
animate={{ animate={{
rotate: 360, rotate: 360,
scale: [1, 1.1, 1], scale: [1, 1.1, 1],
}} }}
transition={{ transition={{
duration: 12, duration: 12,
repeat: Infinity, repeat: Infinity,
ease: "linear" ease: "linear"
}} }}
> >
<Circle className="w-full h-full" /> <Circle className="w-full h-full" />
</motion.div> </motion.div>
<motion.div <motion.div
className="absolute bottom-[-40%] left-[-30%] w-[90%] h-[90%] opacity-[0.05]" className="absolute bottom-[-40%] left-[-30%] w-[90%] h-[90%] opacity-[0.05]"
animate={{ animate={{
rotate: -360, rotate: -360,
scale: [1, 1.2, 1], scale: [1, 1.2, 1],
}} }}
transition={{ transition={{
duration: 15, duration: 15,
repeat: Infinity, repeat: Infinity,
ease: "linear" ease: "linear"
}} }}
> >
<Circle className="w-full h-full" /> <Circle className="w-full h-full" />
@ -134,7 +138,7 @@ export function SignUp({ onSwitchToLogin }: { onSwitchToLogin: () => void }) {
</div> </div>
<div className="w-full max-w-md relative mt-24 md:mt-0"> <div className="w-full max-w-md relative mt-24 md:mt-0">
<div className={`w-full p-6 md:p-8 rounded-2xl backdrop-blur-sm ${theme.cardBackground} relative z-10 <div className={`w-full p-6 md:p-8 rounded-2xl backdrop-blur-sm ${theme.cardBackground} relative z-10
border border-gray-100 dark:border-gray-800 shadow-xl`}> border border-gray-100 dark:border-gray-800 shadow-xl`}>
<h2 className={`text-2xl font-bold mb-6 ${theme.text} md:block hidden`}>Sign Up</h2> <h2 className={`text-2xl font-bold mb-6 ${theme.text} md:block hidden`}>Sign Up</h2>
{error && ( {error && (
@ -149,7 +153,7 @@ export function SignUp({ onSwitchToLogin }: { onSwitchToLogin: () => void }) {
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder="Email" placeholder="Email"
className={`w-full px-4 py-3 rounded-xl ${theme.input} transition-all duration-200 className={`w-full px-4 py-3 rounded-xl ${theme.input} transition-all duration-200
focus:ring-2 focus:ring-black dark:focus:ring-white border-gray-200 dark:border-gray-700`} focus:ring-2 focus:ring-black dark:focus:ring-white border-gray-200 dark:border-gray-700`}
required required
/> />
@ -160,8 +164,8 @@ export function SignUp({ onSwitchToLogin }: { onSwitchToLogin: () => void }) {
value={password} value={password}
onChange={handlePasswordChange} onChange={handlePasswordChange}
placeholder="Password" placeholder="Password"
className={`w-full px-4 py-3 rounded-xl ${theme.input} transition-all duration-200 className={`w-full px-4 py-3 rounded-xl ${theme.input} transition-all duration-200
focus:ring-2 focus:ring-black dark:focus:ring-white focus:ring-2 focus:ring-black dark:focus:ring-white
${passwordError ? 'border-red-500' : 'border-gray-200 dark:border-gray-700'}`} ${passwordError ? 'border-red-500' : 'border-gray-200 dark:border-gray-700'}`}
required required
/> />
@ -182,7 +186,7 @@ export function SignUp({ onSwitchToLogin }: { onSwitchToLogin: () => void }) {
</div> </div>
<button <button
type="submit" type="submit"
className={`w-full py-3 rounded-xl bg-black text-white hover:bg-gray-800 className={`w-full py-3 rounded-xl bg-black text-white hover:bg-gray-800
transition-all duration-200 shadow-lg hover:shadow-xl`} transition-all duration-200 shadow-lg hover:shadow-xl`}
> >
Create Account Create Account
@ -202,4 +206,4 @@ export function SignUp({ onSwitchToLogin }: { onSwitchToLogin: () => void }) {
</div> </div>
</div> </div>
); );
} }

View file

@ -11,53 +11,43 @@ export const useHabits = () => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const { user } = useAuth(); const { user } = useAuth();
useEffect(() => {
if (user) {
fetchHabits();
} else {
setHabits([]);
setLoading(false);
}
}, [user?.id]);
const fetchHabits = async () => { const fetchHabits = async () => {
if (!user) { if (!user) {
setHabits([]); setHabits([]);
setLoading(false); setLoading(false);
return; return;
} }
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
const { data, error } = await supabase // Fetch habits
const { data: habitsData, error: habitsError } = await supabase
.from('habits') .from('habits')
.select(` .select('*')
id,
name,
created_at,
best_streak,
habit_completions (
completion_date
)
`)
.eq('user_id', user.id) .eq('user_id', user.id)
.order('created_at', { ascending: false }); .order('created_at', { ascending: true });
if (error) throw error; if (habitsError) throw habitsError;
const formattedHabits = data.map(habit => ({ // Fetch all completions for user's habits
id: habit.id, const { data: completionsData, error: completionsError } = await supabase
name: habit.name, .from('habit_completions')
created_at: habit.created_at, .select('*')
best_streak: habit.best_streak, .eq('user_id', user.id);
completedDates: habit.habit_completions?.map(
(completion: { completion_date: string }) => completion.completion_date if (completionsError) throw completionsError;
) || []
// Combine habits with their completions
const habitsWithCompletions = habitsData.map(habit => ({
...habit,
completedDates: completionsData
.filter(completion => completion.habit_id === habit.id)
.map(completion => completion.completion_date)
})); }));
setHabits(formattedHabits); setHabits(habitsWithCompletions);
} catch (err) { } catch (err) {
console.error('Error fetching habits:', err); console.error('Error fetching habits:', err);
setError(err instanceof Error ? err.message : 'Failed to fetch habits'); setError(err instanceof Error ? err.message : 'Failed to fetch habits');
@ -66,20 +56,67 @@ export const useHabits = () => {
} }
}; };
// Set up real-time subscription for habits and completions
useEffect(() => {
if (!user) return;
// Subscribe to habits changes
const habitsSubscription = supabase
.channel('habits-changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'habits',
filter: `user_id=eq.${user.id}`
},
() => {
fetchHabits();
}
)
.subscribe();
// Subscribe to habit completions changes
const completionsSubscription = supabase
.channel('completions-changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'habit_completions',
filter: `user_id=eq.${user.id}`
},
() => {
fetchHabits();
}
)
.subscribe();
// Initial fetch
fetchHabits();
// Cleanup subscriptions
return () => {
habitsSubscription.unsubscribe();
completionsSubscription.unsubscribe();
};
}, [user]);
const addHabit = async (name: string): Promise<boolean> => { const addHabit = async (name: string): Promise<boolean> => {
if (!user || !name.trim()) return false; if (!user || !name.trim()) return false;
try { try {
const { data, error } = await supabase const { error } = await supabase
.from('habits') .from('habits')
.insert([{ .insert([{
name: name.trim(), name: name.trim(),
user_id: user.id, user_id: user.id,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
best_streak: 0 best_streak: 0
}]) }])
.select() .select();
.single();
if (error) throw error; if (error) throw error;
@ -94,11 +131,12 @@ export const useHabits = () => {
const toggleHabit = async (id: number, date: string): Promise<void> => { const toggleHabit = async (id: number, date: string): Promise<void> => {
if (!user) return; if (!user) return;
try { try {
const isCompleted = habits const habit = habits.find(h => h.id === id);
.find(h => h.id === id) if (!habit) throw new Error('Habit not found');
?.completedDates.includes(date);
const isCompleted = habit.completedDates.includes(date);
if (isCompleted) { if (isCompleted) {
// Remove completion // Remove completion
@ -106,7 +144,8 @@ export const useHabits = () => {
.from('habit_completions') .from('habit_completions')
.delete() .delete()
.eq('habit_id', id) .eq('habit_id', id)
.eq('completion_date', date); .eq('completion_date', date)
.eq('user_id', user.id);
if (error) throw error; if (error) throw error;
} else { } else {
@ -135,18 +174,17 @@ export const useHabits = () => {
}) })
); );
// Fetch updated data to ensure consistency
await fetchHabits();
} catch (err) { } catch (err) {
console.error('Error toggling habit:', err); console.error('Error toggling habit:', err);
setError(err instanceof Error ? err.message : 'Failed to toggle habit'); setError(err instanceof Error ? err.message : 'Failed to toggle habit');
// Refresh habits to ensure consistency
await fetchHabits(); await fetchHabits();
} }
}; };
const updateHabit = async (id: number, name: string): Promise<void> => { const updateHabit = async (id: number, name: string): Promise<void> => {
if (!user || !name.trim()) return; if (!user || !name.trim()) return;
try { try {
const { error } = await supabase const { error } = await supabase
.from('habits') .from('habits')
@ -172,7 +210,7 @@ export const useHabits = () => {
const deleteHabit = async (id: number): Promise<void> => { const deleteHabit = async (id: number): Promise<void> => {
if (!user) return; if (!user) return;
try { try {
const { error } = await supabase const { error } = await supabase
.from('habits') .from('habits')
@ -196,10 +234,10 @@ export const useHabits = () => {
habits, habits,
loading, loading,
error, error,
fetchHabits,
addHabit, addHabit,
toggleHabit, toggleHabit,
updateHabit, updateHabit,
deleteHabit deleteHabit,
fetchHabits
}; };
}; };

View file

@ -5,21 +5,21 @@ export const formatDate = (date: Date): string => {
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`; return `${year}-${month}-${day}`;
}; };
// Get day index (0-6) where Monday is 0 and Sunday is 6 // Get day index (0-6) where Monday is 0 and Sunday is 6
export const getDayIndex = (date: Date): number => { export const getDayIndex = (date: Date): number => {
const day = date.getDay(); const day = date.getDay();
return day === 0 ? 6 : day - 1; return day === 0 ? 6 : day - 1;
}; };
// Get dates for current week starting from Monday // Get dates for current week starting from Monday
export const getWeekDates = (baseDate: Date = new Date()): string[] => { export const getWeekDates = (baseDate: Date = new Date()): string[] => {
const current = new Date(baseDate); const current = new Date(baseDate);
const dayIndex = getDayIndex(current); const dayIndex = getDayIndex(current);
// Adjust to Monday // Adjust to Monday
current.setDate(current.getDate() - dayIndex); current.setDate(current.getDate() - dayIndex);
// Generate dates starting from Monday // Generate dates starting from Monday
const dates = []; const dates = [];
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
@ -27,16 +27,16 @@ export const formatDate = (date: Date): string => {
date.setDate(current.getDate() + i); date.setDate(current.getDate() + i);
dates.push(formatDate(date)); dates.push(formatDate(date));
} }
return dates; return dates;
}; };
// Get the first day of the month adjusted for Monday start // Get the first day of the month adjusted for Monday start
export const getFirstDayOfMonth = (year: number, month: number): number => { export const getFirstDayOfMonth = (year: number, month: number): number => {
const date = new Date(year, month, 1); const date = new Date(year, month, 1);
return getDayIndex(date); return getDayIndex(date);
}; };
// Check if two dates are the same day // Check if two dates are the same day
export const isSameDay = (date1: Date, date2: Date): boolean => { export const isSameDay = (date1: Date, date2: Date): boolean => {
return ( return (
@ -44,4 +44,4 @@ export const formatDate = (date: Date): string => {
date1.getMonth() === date2.getMonth() && date1.getMonth() === date2.getMonth() &&
date1.getFullYear() === date2.getFullYear() date1.getFullYear() === date2.getFullYear()
); );
}; };