// app/(tabs)/history/calendar.tsx import React, { useState, useEffect } from 'react'; import { View, ScrollView, ActivityIndicator, TouchableOpacity, Pressable, RefreshControl } from 'react-native'; import { Text } from '@/components/ui/text'; import { useSQLiteContext } from 'expo-sqlite'; import { Workout } from '@/types/workout'; import { format, isSameDay } from 'date-fns'; import { Card, CardContent } from '@/components/ui/card'; import { cn } from '@/lib/utils'; import WorkoutCard from '@/components/workout/WorkoutCard'; import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react-native'; import { useNDKCurrentUser } from '@/lib/hooks/useNDK'; import { useWorkoutHistory } from '@/lib/hooks/useWorkoutHistory'; // Add custom styles for 1/7 width (for calendar days) const styles = { calendarDay: "w-[14.28%]" }; // Week days for the calendar view - Fixed the duplicate key issue const WEEK_DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; // Define colors for icons and styling const primaryColor = "#8b5cf6"; // Purple color const mutedColor = "#9ca3af"; // Gray color const primaryBgColor = "rgba(139, 92, 246, 0.2)"; // Semi-transparent purple for date highlights // Mock data for when database tables aren't yet created const mockWorkouts: Workout[] = [ { id: '1', title: 'Push 1', type: 'strength', exercises: [], startTime: new Date('2025-03-07T10:00:00').getTime(), endTime: new Date('2025-03-07T11:47:00').getTime(), isCompleted: true, created_at: new Date('2025-03-07T10:00:00').getTime(), availability: { source: ['local'] }, totalVolume: 9239 }, { id: '2', title: 'Pull 1', type: 'strength', exercises: [], startTime: new Date('2025-03-05T14:00:00').getTime(), endTime: new Date('2025-03-05T15:36:00').getTime(), isCompleted: true, created_at: new Date('2025-03-05T14:00:00').getTime(), availability: { source: ['local'] }, totalVolume: 1396 } ]; export default function CalendarScreen() { const db = useSQLiteContext(); const [workouts, setWorkouts] = useState([]); const [selectedMonth, setSelectedMonth] = useState(new Date()); const [selectedDate, setSelectedDate] = useState(new Date()); const [isLoading, setIsLoading] = useState(true); const [workoutDates, setWorkoutDates] = useState>(new Set()); const [useMockData, setUseMockData] = useState(false); const [refreshing, setRefreshing] = useState(false); const { isAuthenticated } = useNDKCurrentUser(); const [includeNostr, setIncludeNostr] = useState(true); // Use the unified workout history hook const { workouts: allWorkouts, loading, refresh, getWorkoutsByDate, service: workoutHistoryService } = useWorkoutHistory({ includeNostr: includeNostr, realtime: true }); // Set workouts from the hook useEffect(() => { if (loading) { setIsLoading(true); } else { setWorkouts(allWorkouts); setIsLoading(false); setRefreshing(false); // Check if we need to use mock data (empty workouts) if (allWorkouts.length === 0) { console.log('No workouts found, using mock data'); setWorkouts(mockWorkouts); setUseMockData(true); } else { setUseMockData(false); } } }, [allWorkouts, loading]); // Pull to refresh handler const onRefresh = React.useCallback(() => { setRefreshing(true); refresh(); }, [refresh]); // Load workout dates for the selected month useEffect(() => { const loadDatesForMonth = async () => { try { const year = selectedMonth.getFullYear(); const month = selectedMonth.getMonth(); let dates: Date[] = []; // If we're using mock data, filter from mock workouts if (useMockData) { dates = workouts .filter(workout => { const date = new Date(workout.startTime); return date.getFullYear() === year && date.getMonth() === month; }) .map(workout => new Date(workout.startTime)); } else { // Use the service to get dates dates = await workoutHistoryService.getWorkoutDatesInMonth(year, month); // Also check all workouts manually as a fallback const manualDates = allWorkouts .filter(workout => { const date = new Date(workout.startTime); return date.getFullYear() === year && date.getMonth() === month; }) .map(workout => new Date(workout.startTime)); // Combine both sets of dates dates = [...dates, ...manualDates]; } // Convert to strings for the Set const dateStrings = dates.map(date => format(date, 'yyyy-MM-dd')); console.log(`Found ${dateStrings.length} workout dates for ${year}-${month+1}:`, dateStrings); setWorkoutDates(new Set(dateStrings)); } catch (error) { console.error('Error getting workout dates:', error); setWorkoutDates(new Set()); } }; loadDatesForMonth(); }, [selectedMonth, workouts, allWorkouts, workoutHistoryService, useMockData]); // Get dates for current month's calendar const getDaysInMonth = (year: number, month: number) => { const date = new Date(year, month, 1); const days: (Date | null)[] = []; let day = 1; // Get the day of week for the first day (0 = Sunday, 1 = Monday, etc.) const firstDayOfWeek = date.getDay() === 0 ? 6 : date.getDay() - 1; // Convert to Monday-based // Add empty days for the beginning of the month for (let i = 0; i < firstDayOfWeek; i++) { days.push(null); } // Add all days in the month while (date.getMonth() === month) { days.push(new Date(year, month, day)); day++; date.setDate(day); } return days; }; const daysInMonth = getDaysInMonth( selectedMonth.getFullYear(), selectedMonth.getMonth() ); const goToPreviousMonth = () => { setSelectedMonth(prev => new Date(prev.getFullYear(), prev.getMonth() - 1, 1)); }; const goToNextMonth = () => { setSelectedMonth(prev => new Date(prev.getFullYear(), prev.getMonth() + 1, 1)); }; // Check if a date has workouts const hasWorkout = (date: Date | null) => { if (!date) return false; const dateString = format(date, 'yyyy-MM-dd'); const result = workoutDates.has(dateString); console.log(`Checking if ${dateString} has workouts: ${result}`); return result; }; // Handle date selection in calendar const handleDateSelect = (date: Date | null) => { if (!date) return; setSelectedDate(date); }; // Get workouts for the selected date const [selectedDateWorkouts, setSelectedDateWorkouts] = useState([]); const [loadingDateWorkouts, setLoadingDateWorkouts] = useState(false); useEffect(() => { const loadWorkoutsForDate = async () => { try { setLoadingDateWorkouts(true); console.log(`Loading workouts for date: ${format(selectedDate, 'yyyy-MM-dd')}`); if (useMockData) { // Use mock data filtering const filtered = workouts.filter(workout => isSameDay(new Date(workout.startTime), selectedDate) ); console.log(`Found ${filtered.length} mock workouts for selected date`); setSelectedDateWorkouts(filtered); } else { // Use the hook's getWorkoutsByDate method console.log('Calling getWorkoutsByDate...'); const dateWorkouts = await getWorkoutsByDate(selectedDate); console.log(`getWorkoutsByDate returned ${dateWorkouts.length} workouts`); // If no workouts found, try filtering from all workouts as a fallback if (dateWorkouts.length === 0 && allWorkouts.length > 0) { console.log('No workouts found with getWorkoutsByDate, trying manual filtering'); const filtered = allWorkouts.filter(workout => isSameDay(new Date(workout.startTime), selectedDate) ); console.log(`Found ${filtered.length} workouts by manual filtering`); setSelectedDateWorkouts(filtered); } else { setSelectedDateWorkouts(dateWorkouts); } } } catch (error) { console.error('Error loading workouts for date:', error); setSelectedDateWorkouts([]); } finally { setLoadingDateWorkouts(false); } }; loadWorkoutsForDate(); }, [selectedDate, workouts, allWorkouts, getWorkoutsByDate, useMockData]); return ( } > {useMockData && ( Showing example data. Your completed workouts will appear here. )} {/* Calendar section */} Workout Calendar {/* Month navigation */} {format(selectedMonth, 'MMMM yyyy')} {/* Week days header - Fixed with unique keys */} {WEEK_DAYS.map(day => ( {day.charAt(0)} ))} {/* Calendar grid */} {daysInMonth.map((date, index) => ( date && handleDateSelect(date)} > {date ? ( {date.getDate()} ) : ( )} ))} {/* Selected date workouts */} {format(selectedDate, 'MMMM d, yyyy')} {isLoading || loadingDateWorkouts ? ( Loading workouts... ) : selectedDateWorkouts.length === 0 ? ( No workouts on this date {!useMockData && ( Complete a workout on this day to see it here )} ) : ( {selectedDateWorkouts.map(workout => ( ))} )} {/* Add bottom padding for better scrolling experience */} ); }