mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-23 01:01:27 +00:00
Fix calendar date highlighting shape and improve workout date detection
This commit is contained in:
parent
3004bcd4f8
commit
a5d98ba251
42
CHANGELOG.md
42
CHANGELOG.md
@ -5,6 +5,39 @@ All notable changes to the POWR project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
# Changelog - March 24, 2025
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Unified workout history service and hook
|
||||||
|
- Created `UnifiedWorkoutHistoryService` that combines functionality from multiple services
|
||||||
|
- Implemented `useWorkoutHistory` hook for simplified data access
|
||||||
|
- Added real-time Nostr updates with subscription support
|
||||||
|
- Improved filtering capabilities across local and Nostr workouts
|
||||||
|
- Enhanced type safety with better interfaces
|
||||||
|
- Added comprehensive migration guide for developers
|
||||||
|
|
||||||
|
## Improved
|
||||||
|
- Consolidated workout history architecture
|
||||||
|
- Reduced code duplication across services
|
||||||
|
- Simplified API for accessing workout data
|
||||||
|
- Enhanced performance with optimized database queries
|
||||||
|
- Better error handling throughout the system
|
||||||
|
- Improved documentation with migration examples
|
||||||
|
- Enhanced calendar view with fallback filtering for dates
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Calendar view now properly shows workouts when clicking on dates
|
||||||
|
- Added fallback mechanism to filter workouts manually if database query returns no results
|
||||||
|
- Improved logging for better debugging
|
||||||
|
- Fixed edge cases where workouts wouldn't appear in the calendar view
|
||||||
|
|
||||||
|
## Removed
|
||||||
|
- Legacy workout history services
|
||||||
|
- Removed `EnhancedWorkoutHistoryService`
|
||||||
|
- Removed `NostrWorkoutHistoryService`
|
||||||
|
- Removed `useNostrWorkoutHistory` hook
|
||||||
|
- Completed migration to unified workout history API
|
||||||
|
|
||||||
# Changelog - March 23, 2025
|
# Changelog - March 23, 2025
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
@ -22,12 +55,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added checks to verify if exercises exist in the database
|
- Added checks to verify if exercises exist in the database
|
||||||
- Fixed TypeScript errors in exercise existence checks
|
- Fixed TypeScript errors in exercise existence checks
|
||||||
- Improved error handling throughout the service
|
- Improved error handling throughout the service
|
||||||
|
- Calendar view UI and functionality
|
||||||
|
- Fixed date highlighting shape to use clean circles instead of hexagons
|
||||||
|
- Removed shadow effects causing visual distortion in calendar dates
|
||||||
|
- Improved workout date detection with better fallback mechanisms
|
||||||
|
- Enhanced exercise name display in workout cards
|
||||||
|
|
||||||
## Improved
|
## Improved
|
||||||
- Enhanced debugging capabilities
|
- Enhanced debugging capabilities
|
||||||
- Added comprehensive logging in EnhancedWorkoutHistoryService
|
- Added comprehensive logging in EnhancedWorkoutHistoryService
|
||||||
- Improved error state handling in workout detail screen
|
- Improved error state handling in workout detail screen
|
||||||
- Better error messages for troubleshooting
|
- Better error messages for troubleshooting
|
||||||
|
- Calendar view reliability
|
||||||
|
- Added fallback date filtering when database queries return no results
|
||||||
|
- Improved workout date detection with combined data sources
|
||||||
|
- Enhanced visual consistency of date highlighting
|
||||||
|
|
||||||
# Changelog - March 22, 2025
|
# Changelog - March 22, 2025
|
||||||
|
|
||||||
|
@ -7,9 +7,10 @@ import { Workout } from '@/types/workout';
|
|||||||
import { format, isSameDay } from 'date-fns';
|
import { format, isSameDay } from 'date-fns';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { WorkoutHistoryService } from '@/lib/db/services/WorkoutHistoryService';
|
|
||||||
import WorkoutCard from '@/components/workout/WorkoutCard';
|
import WorkoutCard from '@/components/workout/WorkoutCard';
|
||||||
import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react-native';
|
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)
|
// Add custom styles for 1/7 width (for calendar days)
|
||||||
const styles = {
|
const styles = {
|
||||||
@ -19,6 +20,11 @@ const styles = {
|
|||||||
// Week days for the calendar view - Fixed the duplicate key issue
|
// Week days for the calendar view - Fixed the duplicate key issue
|
||||||
const WEEK_DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
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
|
// Mock data for when database tables aren't yet created
|
||||||
const mockWorkouts: Workout[] = [
|
const mockWorkouts: Workout[] = [
|
||||||
{
|
{
|
||||||
@ -57,50 +63,50 @@ export default function CalendarScreen() {
|
|||||||
const [useMockData, setUseMockData] = useState(false);
|
const [useMockData, setUseMockData] = useState(false);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
// Initialize workout history service
|
const { isAuthenticated } = useNDKCurrentUser();
|
||||||
const workoutHistoryService = React.useMemo(() => new WorkoutHistoryService(db), [db]);
|
const [includeNostr, setIncludeNostr] = useState(true);
|
||||||
|
|
||||||
// Load workouts function
|
// Use the unified workout history hook
|
||||||
const loadWorkouts = async () => {
|
const {
|
||||||
try {
|
workouts: allWorkouts,
|
||||||
|
loading,
|
||||||
|
refresh,
|
||||||
|
getWorkoutsByDate,
|
||||||
|
service: workoutHistoryService
|
||||||
|
} = useWorkoutHistory({
|
||||||
|
includeNostr: includeNostr,
|
||||||
|
realtime: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set workouts from the hook
|
||||||
|
useEffect(() => {
|
||||||
|
if (loading) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const allWorkouts = await workoutHistoryService.getAllWorkouts();
|
} else {
|
||||||
setWorkouts(allWorkouts);
|
setWorkouts(allWorkouts);
|
||||||
setUseMockData(false);
|
setIsLoading(false);
|
||||||
} catch (error) {
|
setRefreshing(false);
|
||||||
console.error('Error loading workouts:', error);
|
|
||||||
|
|
||||||
// Check if the error is about missing tables
|
// Check if we need to use mock data (empty workouts)
|
||||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
if (allWorkouts.length === 0) {
|
||||||
if (errorMsg.includes('no such table')) {
|
console.log('No workouts found, using mock data');
|
||||||
console.log('Using mock data because workout tables not yet created');
|
|
||||||
setWorkouts(mockWorkouts);
|
setWorkouts(mockWorkouts);
|
||||||
setUseMockData(true);
|
setUseMockData(true);
|
||||||
} else {
|
} else {
|
||||||
// For other errors, just show empty state
|
|
||||||
setWorkouts([]);
|
|
||||||
setUseMockData(false);
|
setUseMockData(false);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
setRefreshing(false);
|
|
||||||
}
|
}
|
||||||
};
|
}, [allWorkouts, loading]);
|
||||||
|
|
||||||
// Initial load workouts
|
|
||||||
useEffect(() => {
|
|
||||||
loadWorkouts();
|
|
||||||
}, [workoutHistoryService]);
|
|
||||||
|
|
||||||
// Pull to refresh handler
|
// Pull to refresh handler
|
||||||
const onRefresh = React.useCallback(() => {
|
const onRefresh = React.useCallback(() => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
loadWorkouts();
|
refresh();
|
||||||
}, []);
|
}, [refresh]);
|
||||||
|
|
||||||
// Load workout dates for selected month
|
// Load workout dates for the selected month
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getWorkoutDatesForMonth = async () => {
|
const loadDatesForMonth = async () => {
|
||||||
try {
|
try {
|
||||||
const year = selectedMonth.getFullYear();
|
const year = selectedMonth.getFullYear();
|
||||||
const month = selectedMonth.getMonth();
|
const month = selectedMonth.getMonth();
|
||||||
@ -116,28 +122,24 @@ export default function CalendarScreen() {
|
|||||||
})
|
})
|
||||||
.map(workout => new Date(workout.startTime));
|
.map(workout => new Date(workout.startTime));
|
||||||
} else {
|
} else {
|
||||||
// Try to use the service method if it exists and table exists
|
// Use the service to get dates
|
||||||
try {
|
dates = await workoutHistoryService.getWorkoutDatesInMonth(year, month);
|
||||||
if (typeof workoutHistoryService.getWorkoutDatesInMonth === 'function') {
|
|
||||||
dates = await workoutHistoryService.getWorkoutDatesInMonth(year, month);
|
// Also check all workouts manually as a fallback
|
||||||
} else {
|
const manualDates = allWorkouts
|
||||||
// Otherwise filter from loaded workouts
|
.filter(workout => {
|
||||||
dates = workouts
|
const date = new Date(workout.startTime);
|
||||||
.filter(workout => {
|
return date.getFullYear() === year && date.getMonth() === month;
|
||||||
const date = new Date(workout.startTime);
|
})
|
||||||
return date.getFullYear() === year && date.getMonth() === month;
|
.map(workout => new Date(workout.startTime));
|
||||||
})
|
|
||||||
.map(workout => new Date(workout.startTime));
|
// Combine both sets of dates
|
||||||
}
|
dates = [...dates, ...manualDates];
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting workout dates:', error);
|
|
||||||
// If table doesn't exist, use empty array
|
|
||||||
dates = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to strings for the Set
|
// Convert to strings for the Set
|
||||||
const dateStrings = dates.map(date => format(date, 'yyyy-MM-dd'));
|
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));
|
setWorkoutDates(new Set(dateStrings));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting workout dates:', error);
|
console.error('Error getting workout dates:', error);
|
||||||
@ -145,8 +147,8 @@ export default function CalendarScreen() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getWorkoutDatesForMonth();
|
loadDatesForMonth();
|
||||||
}, [selectedMonth, workouts, workoutHistoryService, useMockData]);
|
}, [selectedMonth, workouts, allWorkouts, workoutHistoryService, useMockData]);
|
||||||
|
|
||||||
// Get dates for current month's calendar
|
// Get dates for current month's calendar
|
||||||
const getDaysInMonth = (year: number, month: number) => {
|
const getDaysInMonth = (year: number, month: number) => {
|
||||||
@ -188,7 +190,10 @@ export default function CalendarScreen() {
|
|||||||
// Check if a date has workouts
|
// Check if a date has workouts
|
||||||
const hasWorkout = (date: Date | null) => {
|
const hasWorkout = (date: Date | null) => {
|
||||||
if (!date) return false;
|
if (!date) return false;
|
||||||
return workoutDates.has(format(date, 'yyyy-MM-dd'));
|
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
|
// Handle date selection in calendar
|
||||||
@ -197,7 +202,7 @@ export default function CalendarScreen() {
|
|||||||
setSelectedDate(date);
|
setSelectedDate(date);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get workouts for selected date
|
// Get workouts for the selected date
|
||||||
const [selectedDateWorkouts, setSelectedDateWorkouts] = useState<Workout[]>([]);
|
const [selectedDateWorkouts, setSelectedDateWorkouts] = useState<Workout[]>([]);
|
||||||
const [loadingDateWorkouts, setLoadingDateWorkouts] = useState(false);
|
const [loadingDateWorkouts, setLoadingDateWorkouts] = useState(false);
|
||||||
|
|
||||||
@ -205,30 +210,31 @@ export default function CalendarScreen() {
|
|||||||
const loadWorkoutsForDate = async () => {
|
const loadWorkoutsForDate = async () => {
|
||||||
try {
|
try {
|
||||||
setLoadingDateWorkouts(true);
|
setLoadingDateWorkouts(true);
|
||||||
|
console.log(`Loading workouts for date: ${format(selectedDate, 'yyyy-MM-dd')}`);
|
||||||
|
|
||||||
if (useMockData) {
|
if (useMockData) {
|
||||||
// Use mock data filtering
|
// Use mock data filtering
|
||||||
const filtered = workouts.filter(workout =>
|
const filtered = workouts.filter(workout =>
|
||||||
isSameDay(new Date(workout.startTime), selectedDate)
|
isSameDay(new Date(workout.startTime), selectedDate)
|
||||||
);
|
);
|
||||||
|
console.log(`Found ${filtered.length} mock workouts for selected date`);
|
||||||
setSelectedDateWorkouts(filtered);
|
setSelectedDateWorkouts(filtered);
|
||||||
} else {
|
} else {
|
||||||
try {
|
// Use the hook's getWorkoutsByDate method
|
||||||
if (typeof workoutHistoryService.getWorkoutsByDate === 'function') {
|
console.log('Calling getWorkoutsByDate...');
|
||||||
// Use the service method if available
|
const dateWorkouts = await getWorkoutsByDate(selectedDate);
|
||||||
const dateWorkouts = await workoutHistoryService.getWorkoutsByDate(selectedDate);
|
console.log(`getWorkoutsByDate returned ${dateWorkouts.length} workouts`);
|
||||||
setSelectedDateWorkouts(dateWorkouts);
|
|
||||||
} else {
|
// If no workouts found, try filtering from all workouts as a fallback
|
||||||
// Fall back to filtering the already loaded workouts
|
if (dateWorkouts.length === 0 && allWorkouts.length > 0) {
|
||||||
const filtered = workouts.filter(workout =>
|
console.log('No workouts found with getWorkoutsByDate, trying manual filtering');
|
||||||
isSameDay(new Date(workout.startTime), selectedDate)
|
const filtered = allWorkouts.filter(workout =>
|
||||||
);
|
isSameDay(new Date(workout.startTime), selectedDate)
|
||||||
setSelectedDateWorkouts(filtered);
|
);
|
||||||
}
|
console.log(`Found ${filtered.length} workouts by manual filtering`);
|
||||||
} catch (error) {
|
setSelectedDateWorkouts(filtered);
|
||||||
// Handle the case where the workout table doesn't exist
|
} else {
|
||||||
console.error('Error loading workouts for date:', error);
|
setSelectedDateWorkouts(dateWorkouts);
|
||||||
setSelectedDateWorkouts([]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -240,7 +246,7 @@ export default function CalendarScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadWorkoutsForDate();
|
loadWorkoutsForDate();
|
||||||
}, [selectedDate, workouts, workoutHistoryService, useMockData]);
|
}, [selectedDate, workouts, allWorkouts, getWorkoutsByDate, useMockData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="flex-1 bg-background">
|
<View className="flex-1 bg-background">
|
||||||
@ -262,7 +268,7 @@ export default function CalendarScreen() {
|
|||||||
{/* Calendar section */}
|
{/* Calendar section */}
|
||||||
<View className="mb-6">
|
<View className="mb-6">
|
||||||
<View className="flex-row items-center mb-4">
|
<View className="flex-row items-center mb-4">
|
||||||
<Calendar size={20} className="text-primary mr-2" />
|
<Calendar size={20} color={primaryColor} style={{ marginRight: 8 }} />
|
||||||
<Text className="text-lg font-semibold">Workout Calendar</Text>
|
<Text className="text-lg font-semibold">Workout Calendar</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -274,7 +280,7 @@ export default function CalendarScreen() {
|
|||||||
onPress={goToPreviousMonth}
|
onPress={goToPreviousMonth}
|
||||||
className="p-2 rounded-full bg-muted"
|
className="p-2 rounded-full bg-muted"
|
||||||
>
|
>
|
||||||
<ChevronLeft size={20} className="text-foreground" />
|
<ChevronLeft size={20} color={mutedColor} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<Text className="text-foreground text-lg font-semibold">
|
<Text className="text-foreground text-lg font-semibold">
|
||||||
@ -285,7 +291,7 @@ export default function CalendarScreen() {
|
|||||||
onPress={goToNextMonth}
|
onPress={goToNextMonth}
|
||||||
className="p-2 rounded-full bg-muted"
|
className="p-2 rounded-full bg-muted"
|
||||||
>
|
>
|
||||||
<ChevronRight size={20} className="text-foreground" />
|
<ChevronRight size={20} color={mutedColor} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -311,18 +317,32 @@ export default function CalendarScreen() {
|
|||||||
<View className="aspect-square items-center justify-center">
|
<View className="aspect-square items-center justify-center">
|
||||||
{date ? (
|
{date ? (
|
||||||
<View
|
<View
|
||||||
className={cn(
|
style={{
|
||||||
"w-8 h-8 rounded-full items-center justify-center",
|
width: 40,
|
||||||
isSameDay(date, selectedDate) && !hasWorkout(date) && "bg-muted",
|
height: 40,
|
||||||
hasWorkout(date) && "bg-primary",
|
borderRadius: 20, // Make it a perfect circle
|
||||||
isSameDay(date, new Date()) && !hasWorkout(date) && !isSameDay(date, selectedDate) && "border border-primary"
|
alignItems: 'center',
|
||||||
)}
|
justifyContent: 'center',
|
||||||
|
backgroundColor: isSameDay(date, selectedDate)
|
||||||
|
? (hasWorkout(date) ? primaryColor : '#f3f4f6') // Selected date
|
||||||
|
: (hasWorkout(date) ? primaryBgColor : 'transparent'), // Date with workout
|
||||||
|
borderWidth: isSameDay(date, new Date()) && !hasWorkout(date) && !isSameDay(date, selectedDate) ? 2 : 0,
|
||||||
|
borderColor: primaryColor,
|
||||||
|
// Remove shadow effects that might be causing the weird shape
|
||||||
|
shadowColor: 'transparent',
|
||||||
|
shadowOffset: { width: 0, height: 0 },
|
||||||
|
shadowOpacity: 0,
|
||||||
|
shadowRadius: 0,
|
||||||
|
elevation: 0
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
className={cn(
|
style={{
|
||||||
"text-foreground",
|
color: isSameDay(date, selectedDate) && hasWorkout(date)
|
||||||
hasWorkout(date) && "text-primary-foreground font-medium"
|
? '#ffffff' // White text for selected date with workout
|
||||||
)}
|
: (hasWorkout(date) ? primaryColor : '#374151'), // Purple text for dates with workouts
|
||||||
|
fontWeight: hasWorkout(date) ? '600' : 'normal'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{date.getDate()}
|
{date.getDate()}
|
||||||
</Text>
|
</Text>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user