// 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 { WorkoutHistoryService } from '@/lib/db/services/WorkoutHIstoryService'; import WorkoutCard from '@/components/workout/WorkoutCard'; import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react-native'; // 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']; // 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); // Initialize workout history service const workoutHistoryService = React.useMemo(() => new WorkoutHistoryService(db), [db]); // Load workouts function const loadWorkouts = async () => { try { setIsLoading(true); const allWorkouts = await workoutHistoryService.getAllWorkouts(); setWorkouts(allWorkouts); setUseMockData(false); } catch (error) { console.error('Error loading workouts:', error); // Check if the error is about missing tables const errorMsg = error instanceof Error ? error.message : String(error); if (errorMsg.includes('no such table')) { console.log('Using mock data because workout tables not yet created'); setWorkouts(mockWorkouts); setUseMockData(true); } else { // For other errors, just show empty state setWorkouts([]); setUseMockData(false); } } finally { setIsLoading(false); setRefreshing(false); } }; // Initial load workouts useEffect(() => { loadWorkouts(); }, [workoutHistoryService]); // Pull to refresh handler const onRefresh = React.useCallback(() => { setRefreshing(true); loadWorkouts(); }, []); // Load workout dates for selected month useEffect(() => { const getWorkoutDatesForMonth = 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 { // Try to use the service method if it exists and table exists try { if (typeof workoutHistoryService.getWorkoutDatesInMonth === 'function') { dates = await workoutHistoryService.getWorkoutDatesInMonth(year, month); } else { // Otherwise filter from loaded workouts dates = workouts .filter(workout => { const date = new Date(workout.startTime); return date.getFullYear() === year && date.getMonth() === month; }) .map(workout => new Date(workout.startTime)); } } catch (error) { console.error('Error getting workout dates:', error); // If table doesn't exist, use empty array dates = []; } } // Convert to strings for the Set const dateStrings = dates.map(date => format(date, 'yyyy-MM-dd')); setWorkoutDates(new Set(dateStrings)); } catch (error) { console.error('Error getting workout dates:', error); setWorkoutDates(new Set()); } }; getWorkoutDatesForMonth(); }, [selectedMonth, workouts, 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; return workoutDates.has(format(date, 'yyyy-MM-dd')); }; // Handle date selection in calendar const handleDateSelect = (date: Date | null) => { if (!date) return; setSelectedDate(date); }; // Get workouts for selected date const [selectedDateWorkouts, setSelectedDateWorkouts] = useState([]); const [loadingDateWorkouts, setLoadingDateWorkouts] = useState(false); useEffect(() => { const loadWorkoutsForDate = async () => { try { setLoadingDateWorkouts(true); if (useMockData) { // Use mock data filtering const filtered = workouts.filter(workout => isSameDay(new Date(workout.startTime), selectedDate) ); setSelectedDateWorkouts(filtered); } else { try { if (typeof workoutHistoryService.getWorkoutsByDate === 'function') { // Use the service method if available const dateWorkouts = await workoutHistoryService.getWorkoutsByDate(selectedDate); setSelectedDateWorkouts(dateWorkouts); } else { // Fall back to filtering the already loaded workouts const filtered = workouts.filter(workout => isSameDay(new Date(workout.startTime), selectedDate) ); setSelectedDateWorkouts(filtered); } } catch (error) { // Handle the case where the workout table doesn't exist console.error('Error loading workouts for date:', error); setSelectedDateWorkouts([]); } } } catch (error) { console.error('Error loading workouts for date:', error); setSelectedDateWorkouts([]); } finally { setLoadingDateWorkouts(false); } }; loadWorkoutsForDate(); }, [selectedDate, workouts, workoutHistoryService, 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 */} ); }