// app/(tabs)/library/templates.tsx import React, { useState } from 'react'; import { View, ScrollView, ActivityIndicator } from 'react-native'; import { router } from 'expo-router'; import { Text } from '@/components/ui/text'; import { Input } from '@/components/ui/input'; import { useFocusEffect } from '@react-navigation/native'; import { Search, Plus, ListFilter } from 'lucide-react-native'; import { FloatingActionButton } from '@/components/shared/FloatingActionButton'; import { NewTemplateSheet } from '@/components/library/NewTemplateSheet'; import { FilterSheet, type FilterOptions, type SourceType } from '@/components/library/FilterSheet'; import { TemplateCard } from '@/components/templates/TemplateCard'; import { ModalTemplateDetails } from '@/components/templates/ModalTemplateDetails'; import { Button } from '@/components/ui/button'; import { Template, TemplateCategory, TemplateExerciseConfig, TemplateExerciseDisplay, toWorkoutTemplate } from '@/types/templates'; import { generateId } from '@/utils/ids'; import { ExerciseDisplay } from '@/types/exercise'; // Enhanced template exercise display that includes the original exercise object interface EnhancedTemplateExerciseDisplay { title: string; targetSets: number; targetReps: number; equipment?: string; notes?: string; exercise: ExerciseDisplay; } import { useWorkoutStore } from '@/stores/workoutStore'; import { useTemplates } from '@/lib/hooks/useTemplates'; import { useIconColor } from '@/lib/theme/iconUtils'; import { useLibraryStore } from '@/lib/stores/libraryStore'; // Default available filters const availableFilters = { equipment: ['Barbell', 'Dumbbell', 'Bodyweight', 'Machine', 'Cables', 'Other'], tags: ['Strength', 'Cardio', 'Mobility', 'Recovery'], source: ['local', 'powr', 'nostr'] as SourceType[] }; // Initial filter state const initialFilters: FilterOptions = { equipment: [], tags: [], source: [] }; export default function TemplatesScreen() { // State for UI elements const [showNewTemplate, setShowNewTemplate] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [filterSheetOpen, setFilterSheetOpen] = useState(false); const [currentFilters, setCurrentFilters] = useState(initialFilters); const [activeFilters, setActiveFilters] = useState(0); const [debugInfo, setDebugInfo] = useState(''); // State for the modal template details const [selectedTemplateId, setSelectedTemplateId] = useState(null); const [showTemplateModal, setShowTemplateModal] = useState(false); // Hooks const { getIconProps } = useIconColor(); const { isActive, isMinimized } = useWorkoutStore(); const { templates, loading, error, silentRefresh, refreshTemplates, createTemplate, updateTemplate, deleteTemplate, archiveTemplate } = useTemplates(); // Check if floating action button should be shown const shouldShowFAB = !isActive || !isMinimized; // Convert WorkoutTemplates to Template format for the UI const formattedTemplates = React.useMemo(() => { return templates.map(template => { // Get favorite status const isFavorite = useWorkoutStore.getState().checkFavoriteStatus(template.id); // Convert to Template format for the UI return { id: template.id, title: template.title, type: template.type, category: template.category, exercises: template.exercises.map(ex => ({ title: ex.exercise.title, targetSets: ex.targetSets || 0, targetReps: ex.targetReps || 0, equipment: ex.exercise.equipment })), tags: template.tags || [], source: template.availability?.source[0] || 'local', isFavorite }; }); }, [templates]); // Refresh templates when the screen is focused useFocusEffect( React.useCallback(() => { // This will refresh without showing loading indicators if possible silentRefresh(); return () => {}; }, [silentRefresh]) ); const handleDelete = async (id: string) => { try { await deleteTemplate(id); } catch (error) { console.error('Error deleting template:', error); } }; const handleTemplatePress = (template: Template) => { // Just open the modal without navigating to a route setSelectedTemplateId(template.id); setShowTemplateModal(true); }; const handleStartWorkout = async (template: Template) => { try { // Start the workout - use the template ID await useWorkoutStore.getState().startWorkoutFromTemplate(template.id); // Navigate to the active workout screen router.push('/(workout)/create'); } catch (error) { console.error("Error starting workout:", error); } }; const handleFavorite = async (template: Template) => { try { const workoutTemplate = toWorkoutTemplate(template); const isFavorite = useWorkoutStore.getState().checkFavoriteStatus(template.id); if (isFavorite) { await useWorkoutStore.getState().removeFavorite(template.id); } else { await useWorkoutStore.getState().addFavorite(workoutTemplate); } // Add this line to trigger a refresh after favorite toggle useLibraryStore.getState().refreshTemplates(); } catch (error) { console.error('Error toggling favorite status:', error); } }; const handleApplyFilters = (filters: FilterOptions) => { setCurrentFilters(filters); const totalFilters = Object.values(filters).reduce( (acc, curr) => acc + curr.length, 0 ); setActiveFilters(totalFilters); }; // Handle modal close const handleModalClose = () => { setShowTemplateModal(false); }; // Handle favorite change from modal const handleModalFavoriteChange = (templateId: string, isFavorite: boolean) => { // The templates will be refreshed automatically through the store }; const handleAddTemplate = (template: Template) => { // The template exercises should already have the exercise property from NewTemplateSheet // We know the exercises have the exercise property because we modified NewTemplateSheet const enhancedExercises = template.exercises as unknown as EnhancedTemplateExerciseDisplay[]; // Convert UI Template to WorkoutTemplate, but preserve exercise IDs const baseWorkoutTemplate = toWorkoutTemplate(template); // Modify the exercises to use the original exercise objects with their IDs const workoutTemplate = { ...baseWorkoutTemplate, exercises: enhancedExercises.map(ex => { // Create a proper TemplateExerciseConfig object const config: TemplateExerciseConfig = { id: generateId(), // ID for the template_exercise relationship exercise: ex.exercise, // Use the original exercise object with its ID targetSets: ex.targetSets, targetReps: ex.targetReps, notes: ex.notes }; return config; }) }; // Create the template createTemplate(workoutTemplate); // Close the sheet setShowNewTemplate(false); }; // Filter templates based on search and applied filters const filteredTemplates = formattedTemplates.filter(template => { // Filter by search query const matchesSearch = !searchQuery || template.title.toLowerCase().includes(searchQuery.toLowerCase()); // Filter by equipment if any selected const matchesEquipment = currentFilters.equipment.length === 0 || (template.exercises && template.exercises.some(ex => currentFilters.equipment.includes(ex.equipment || '') )); // Filter by tags if any selected const matchesTags = currentFilters.tags.length === 0 || (template.tags && template.tags.some(tag => currentFilters.tags.includes(tag) )); // Filter by source if any selected const matchesSource = currentFilters.source.length === 0 || currentFilters.source.includes(template.source as SourceType); return matchesSearch && matchesEquipment && matchesTags && matchesSource; }); // Separate favorites and regular templates const favoriteTemplates = filteredTemplates.filter(t => t.isFavorite); const regularTemplates = filteredTemplates.filter(t => !t.isFavorite); return ( {/* Search bar with filter button */} {/* Filter Sheet */} setFilterSheetOpen(false)} options={currentFilters} onApplyFilters={handleApplyFilters} availableFilters={availableFilters} /> {/* Templates list with loading state */} {loading ? ( Loading templates... ) : ( {/* Favorites Section */} {favoriteTemplates.length > 0 && ( Favorites {favoriteTemplates.map(template => ( handleTemplatePress(template)} onDelete={() => handleDelete(template.id)} onFavorite={() => handleFavorite(template)} onStartWorkout={() => handleStartWorkout(template)} /> ))} )} {/* All Templates Section */} All Templates {regularTemplates.length > 0 ? ( {regularTemplates.map(template => ( handleTemplatePress(template)} onDelete={() => handleDelete(template.id)} onFavorite={() => handleFavorite(template)} onStartWorkout={() => handleStartWorkout(template)} /> ))} ) : ( {formattedTemplates.length > 0 ? 'No templates match your current filters.' : 'No templates found. Create a new workout template by clicking the + button.'} )} {/* Add some bottom padding for FAB */} )} {shouldShowFAB && ( setShowNewTemplate(true)} /> )} {/* Template Details Modal */} {/* New Template Sheet */} setShowNewTemplate(false)} onSubmit={handleAddTemplate} /> ); }