diff --git a/CHANGELOG.md b/CHANGELOG.md index 547b16d..5ffbde3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Alphabetical quick scroll in exercise library + - Dynamic letter highlighting for available sections + - Smooth scrolling to selected sections + - Sticky section headers for better navigation - Basic exercise template creation functionality - Input validation for required fields - Schema-compliant field constraints @@ -18,6 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added proper error types and propagation ### Changed +- Improved exercise library interface + - Removed "Recent Exercises" section for cleaner UI + - Added alphabetical section organization + - Enhanced keyboard handling for input fields + - Increased description text area size - Updated NewExerciseScreen with constrained inputs - Added dropdowns for equipment selection - Added movement pattern selection @@ -28,6 +37,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Enhanced transaction rollback handling - Added detailed debug logging +### Fixed +- Exercise deletion functionality +- Keyboard overlap issues in exercise creation form +- SQLite transaction handling for exercise operations + ### Technical Details 1. Database Schema Enforcement: - Added CHECK constraints for equipment types diff --git a/app/(tabs)/library/exercises.tsx b/app/(tabs)/library/exercises.tsx index 7fb27ba..5a1b032 100644 --- a/app/(tabs)/library/exercises.tsx +++ b/app/(tabs)/library/exercises.tsx @@ -1,6 +1,6 @@ // app/(tabs)/library/exercises.tsx -import React, { useState, useEffect } from 'react'; -import { View, ScrollView, SectionList } from 'react-native'; +import React, { useState, useEffect, useRef } from 'react'; +import { View, SectionList, TouchableOpacity, SectionListData } from 'react-native'; import { Text } from '@/components/ui/text'; import { ExerciseCard } from '@/components/exercises/ExerciseCard'; import { FloatingActionButton } from '@/components/shared/FloatingActionButton'; @@ -10,17 +10,46 @@ import { Exercise, BaseExercise } from '@/types/exercise'; import { useSQLiteContext } from 'expo-sqlite'; import { ExerciseService } from '@/lib/db/services/ExerciseService'; +interface ExerciseSection { + title: string; + data: Exercise[]; +} + export default function ExercisesScreen() { const db = useSQLiteContext(); const exerciseService = React.useMemo(() => new ExerciseService(db), [db]); + const sectionListRef = useRef(null); const [exercises, setExercises] = useState([]); + const [sections, setSections] = useState([]); const [showNewExercise, setShowNewExercise] = useState(false); useEffect(() => { loadExercises(); }, []); + useEffect(() => { + // Organize exercises into sections when exercises array changes + const exercisesByLetter = exercises.reduce((acc, exercise) => { + const firstLetter = exercise.title[0].toUpperCase(); + if (!acc[firstLetter]) { + acc[firstLetter] = []; + } + acc[firstLetter].push(exercise); + return acc; + }, {} as Record); + + // Create sections array sorted alphabetically + const newSections = Object.entries(exercisesByLetter) + .map(([letter, exercises]) => ({ + title: letter, + data: exercises.sort((a, b) => a.title.localeCompare(b.title)) + })) + .sort((a, b) => a.title.localeCompare(b.title)); + + setSections(newSections); + }, [exercises]); + const loadExercises = async () => { try { const loadedExercises = await exerciseService.getAllExercises(); @@ -57,40 +86,64 @@ export default function ExercisesScreen() { console.log('Selected exercise:', exerciseId); }; + const scrollToSection = (letter: string) => { + const sectionIndex = sections.findIndex(section => section.title === letter); + if (sectionIndex !== -1) { + sectionListRef.current?.scrollToLocation({ + sectionIndex, + itemIndex: 0, + animated: true, + viewOffset: 20 + }); + } + }; + const alphabet = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + const availableLetters = new Set(sections.map(section => section.title)); return ( {alphabet.map((letter) => ( - { - // TODO: Implement scroll to section - console.log('Scroll to:', letter); - }} + onPress={() => scrollToSection(letter)} + className="py-0.5" > - {letter} - + + {letter} + + ))} - - - All Exercises - - {exercises.map(exercise => ( - handleExercisePress(exercise.id)} - onDelete={() => handleDelete(exercise.id)} - /> - ))} + item.id} + renderSectionHeader={({ section }) => ( + + {section.title} - - + )} + renderItem={({ item }) => ( + + handleExercisePress(item.id)} + onDelete={() => handleDelete(item.id)} + /> + + )} + stickySectionHeadersEnabled + className="flex-1" + /> ([]); const [testResults, setTestResults] = useState<{ success: boolean; message: string; @@ -49,8 +55,20 @@ export default function ProgramsScreen() { useEffect(() => { checkDatabase(); + inspectDatabase(); }, []); + const inspectDatabase = async () => { + try { + const result = await db.getAllAsync( + "SELECT name, sql FROM sqlite_master WHERE type='table'" + ); + setSchemas(result); + } catch (error) { + console.error('Error inspecting database:', error); + } + }; + const checkDatabase = async () => { try { // Check schema_version table @@ -99,6 +117,7 @@ export default function ProgramsScreen() { // Refresh database status checkDatabase(); + inspectDatabase(); } catch (error) { console.error('Error resetting database:', error); setTestResults({ @@ -186,6 +205,35 @@ export default function ProgramsScreen() { Database Debug Panel + {/* Schema Inspector Card */} + + + + + Database Schema ({Platform.OS}) + + + + + {schemas.map((table) => ( + + {table.name} + + {table.sql} + + + ))} + + + + + + {/* Status Card */} @@ -215,6 +263,7 @@ export default function ProgramsScreen() { + {/* Operations Card */} diff --git a/components/DatabaseDebug.tsx b/components/DatabaseDebug.tsx index 6d109f7..dde5c56 100644 --- a/components/DatabaseDebug.tsx +++ b/components/DatabaseDebug.tsx @@ -150,7 +150,7 @@ export default function DatabaseDebug() { return ( - + {/* Add mt-4 to create spacing */} Database Status diff --git a/components/library/NewExerciseSheet.tsx b/components/library/NewExerciseSheet.tsx index bb16520..3db3f79 100644 --- a/components/library/NewExerciseSheet.tsx +++ b/components/library/NewExerciseSheet.tsx @@ -1,13 +1,14 @@ // components/library/NewExerciseSheet.tsx import React from 'react'; -import { View } from 'react-native'; +import { View, KeyboardAvoidingView, Platform, ScrollView } from 'react-native'; import { Text } from '@/components/ui/text'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'; -import { generateId } from '@/utils/ids'; import { BaseExercise, ExerciseType, ExerciseCategory, Equipment } from '@/types/exercise'; import { StorageSource } from '@/types/shared'; +import { Textarea } from '@/components/ui/textarea'; +import { generateId } from '@/utils/ids'; interface NewExerciseSheetProps { isOpen: boolean; @@ -93,87 +94,93 @@ export function NewExerciseSheet({ isOpen, onClose, onSubmit }: NewExerciseSheet New Exercise - - - Exercise Name - setFormData(prev => ({ ...prev, title: text }))} - placeholder="e.g., Barbell Back Squat" - /> - - - - Type - - {EXERCISE_TYPES.map((type) => ( - - ))} + + + + Exercise Name + setFormData(prev => ({ ...prev, title: text }))} + placeholder="e.g., Barbell Back Squat" + /> - - - Category - - {CATEGORIES.map((category) => ( - - ))} + + Type + + {EXERCISE_TYPES.map((type) => ( + + ))} + - - - Equipment - - {EQUIPMENT_OPTIONS.map((eq) => ( - - ))} + + Category + + {CATEGORIES.map((category) => ( + + ))} + - - - Description - setFormData(prev => ({ ...prev, description: text }))} - placeholder="Exercise description..." - multiline - numberOfLines={4} - /> - + + Equipment + + {EQUIPMENT_OPTIONS.map((eq) => ( + + ))} + + - - + + Description +