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

@ -118,6 +118,45 @@ 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 (
<>
<div className={`
@ -209,12 +248,21 @@ export const Calendar: React.FC<CalendarProps> = ({
return (
<div
key={date}
onClick={() => {
if (isMobile) {
setSelectedDate({
date,
completedHabits,
incompleteHabits
});
}
}}
className={`
border rounded-lg relative
${theme.border}
${isCurrentMonth ? theme.calendar.day.default : theme.calendar.day.otherMonth}
${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={`
@ -284,7 +332,81 @@ export const Calendar: React.FC<CalendarProps> = ({
</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(
<div
ref={tooltipRef}

View file

@ -48,8 +48,12 @@ export function SignUp({ onSwitchToLogin }: { onSwitchToLogin: () => void }) {
setError('');
setPasswordError(null);
await signUp(email, password);
} catch (err) {
setError('Failed to create an account');
} catch (err: unknown) {
// Handle the error message
const errorMessage = err instanceof Error
? err.message
: 'Failed to create an account';
setError(errorMessage);
}
};

View file

@ -11,15 +11,6 @@ export const useHabits = () => {
const [error, setError] = useState<string | null>(null);
const { user } = useAuth();
useEffect(() => {
if (user) {
fetchHabits();
} else {
setHabits([]);
setLoading(false);
}
}, [user?.id]);
const fetchHabits = async () => {
if (!user) {
setHabits([]);
@ -31,33 +22,32 @@ export const useHabits = () => {
setLoading(true);
setError(null);
const { data, error } = await supabase
// Fetch habits
const { data: habitsData, error: habitsError } = await supabase
.from('habits')
.select(`
id,
name,
created_at,
best_streak,
habit_completions (
completion_date
)
`)
.select('*')
.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 => ({
id: habit.id,
name: habit.name,
created_at: habit.created_at,
best_streak: habit.best_streak,
completedDates: habit.habit_completions?.map(
(completion: { completion_date: string }) => completion.completion_date
) || []
// Fetch all completions for user's habits
const { data: completionsData, error: completionsError } = await supabase
.from('habit_completions')
.select('*')
.eq('user_id', user.id);
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) {
console.error('Error fetching habits:', err);
setError(err instanceof Error ? err.message : 'Failed to fetch habits');
@ -66,11 +56,59 @@ 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> => {
if (!user || !name.trim()) return false;
try {
const { data, error } = await supabase
const { error } = await supabase
.from('habits')
.insert([{
name: name.trim(),
@ -78,8 +116,7 @@ export const useHabits = () => {
created_at: new Date().toISOString(),
best_streak: 0
}])
.select()
.single();
.select();
if (error) throw error;
@ -96,9 +133,10 @@ export const useHabits = () => {
if (!user) return;
try {
const isCompleted = habits
.find(h => h.id === id)
?.completedDates.includes(date);
const habit = habits.find(h => h.id === id);
if (!habit) throw new Error('Habit not found');
const isCompleted = habit.completedDates.includes(date);
if (isCompleted) {
// Remove completion
@ -106,7 +144,8 @@ export const useHabits = () => {
.from('habit_completions')
.delete()
.eq('habit_id', id)
.eq('completion_date', date);
.eq('completion_date', date)
.eq('user_id', user.id);
if (error) throw error;
} else {
@ -135,11 +174,10 @@ export const useHabits = () => {
})
);
// Fetch updated data to ensure consistency
await fetchHabits();
} catch (err) {
console.error('Error toggling habit:', err);
setError(err instanceof Error ? err.message : 'Failed to toggle habit');
// Refresh habits to ensure consistency
await fetchHabits();
}
};
@ -196,10 +234,10 @@ export const useHabits = () => {
habits,
loading,
error,
fetchHabits,
addHabit,
toggleHabit,
updateHabit,
deleteHabit
deleteHabit,
fetchHabits
};
};