diff --git a/src/app/lib/storage/workoutStorage.ts b/src/app/lib/storage/workoutStorage.ts new file mode 100644 index 0000000..bfdb2ea --- /dev/null +++ b/src/app/lib/storage/workoutStorage.ts @@ -0,0 +1,309 @@ +/** + * Workout and metrics data persistence layer + * Handles saving and retrieving workout data from localStorage + */ + +export interface WorkoutSet { + reps: number; + weight: number; + timestamp: number; +} + +export interface WorkoutSession { + id: string; + exerciseName: string; + date: string; + sets: WorkoutSet[]; + duration: number; // in seconds + caloriesBurned: number; + notes?: string; +} + +export interface CardioSession { + id: string; + type: 'running' | 'walking' | 'cycling'; + date: string; + distance: number; // in km + duration: number; // in seconds + caloriesBurned: number; + pace: number; // km/h + steps?: number; + route?: string; +} + +export interface NutritionLog { + id: string; + date: string; + calories: number; + protein: number; + carbs: number; + fats: number; + meals: string[]; +} + +export interface UserMetrics { + totalWorkouts: number; + totalCardioDistance: number; + totalCaloriesBurned: number; + currentStreak: number; + personalRecords: Record; + lastUpdated: number; +} + +const STORAGE_KEYS = { + WORKOUT_SESSIONS: 'fitflow_workout_sessions', + CARDIO_SESSIONS: 'fitflow_cardio_sessions', + NUTRITION_LOGS: 'fitflow_nutrition_logs', + USER_METRICS: 'fitflow_user_metrics', +}; + +/** + * Save a workout session to localStorage + */ +export const saveWorkoutSession = (session: WorkoutSession): void => { + if (typeof window === 'undefined') return; + + try { + const sessions = getWorkoutSessions(); + sessions.push(session); + localStorage.setItem(STORAGE_KEYS.WORKOUT_SESSIONS, JSON.stringify(sessions)); + updateUserMetrics(); + } catch (error) { + console.error('Error saving workout session:', error); + } +}; + +/** + * Get all workout sessions from localStorage + */ +export const getWorkoutSessions = (): WorkoutSession[] => { + if (typeof window === 'undefined') return []; + + try { + const data = localStorage.getItem(STORAGE_KEYS.WORKOUT_SESSIONS); + return data ? JSON.parse(data) : []; + } catch (error) { + console.error('Error retrieving workout sessions:', error); + return []; + } +}; + +/** + * Save a cardio session to localStorage + */ +export const saveCardioSession = (session: CardioSession): void => { + if (typeof window === 'undefined') return; + + try { + const sessions = getCardioSessions(); + sessions.push(session); + localStorage.setItem(STORAGE_KEYS.CARDIO_SESSIONS, JSON.stringify(sessions)); + updateUserMetrics(); + } catch (error) { + console.error('Error saving cardio session:', error); + } +}; + +/** + * Get all cardio sessions from localStorage + */ +export const getCardioSessions = (): CardioSession[] => { + if (typeof window === 'undefined') return []; + + try { + const data = localStorage.getItem(STORAGE_KEYS.CARDIO_SESSIONS); + return data ? JSON.parse(data) : []; + } catch (error) { + console.error('Error retrieving cardio sessions:', error); + return []; + } +}; + +/** + * Save a nutrition log to localStorage + */ +export const saveNutritionLog = (log: NutritionLog): void => { + if (typeof window === 'undefined') return; + + try { + const logs = getNutritionLogs(); + logs.push(log); + localStorage.setItem(STORAGE_KEYS.NUTRITION_LOGS, JSON.stringify(logs)); + updateUserMetrics(); + } catch (error) { + console.error('Error saving nutrition log:', error); + } +}; + +/** + * Get all nutrition logs from localStorage + */ +export const getNutritionLogs = (): NutritionLog[] => { + if (typeof window === 'undefined') return []; + + try { + const data = localStorage.getItem(STORAGE_KEYS.NUTRITION_LOGS); + return data ? JSON.parse(data) : []; + } catch (error) { + console.error('Error retrieving nutrition logs:', error); + return []; + } +}; + +/** + * Get user metrics from localStorage + */ +export const getUserMetrics = (): UserMetrics => { + if (typeof window === 'undefined') { + return { + totalWorkouts: 0, + totalCardioDistance: 0, + totalCaloriesBurned: 0, + currentStreak: 0, + personalRecords: {}, + lastUpdated: Date.now(), + }; + } + + try { + const data = localStorage.getItem(STORAGE_KEYS.USER_METRICS); + return data + ? JSON.parse(data) + : { + totalWorkouts: 0, + totalCardioDistance: 0, + totalCaloriesBurned: 0, + currentStreak: 0, + personalRecords: {}, + lastUpdated: Date.now(), + }; + } catch (error) { + console.error('Error retrieving user metrics:', error); + return { + totalWorkouts: 0, + totalCardioDistance: 0, + totalCaloriesBurned: 0, + currentStreak: 0, + personalRecords: {}, + lastUpdated: Date.now(), + }; + } +}; + +/** + * Update user metrics based on saved sessions + */ +export const updateUserMetrics = (): void => { + if (typeof window === 'undefined') return; + + try { + const workoutSessions = getWorkoutSessions(); + const cardioSessions = getCardioSessions(); + const nutritionLogs = getNutritionLogs(); + + let totalWorkouts = workoutSessions.length; + let totalCardioDistance = cardioSessions.reduce((sum, session) => sum + session.distance, 0); + let totalCaloriesBurned = workoutSessions.reduce((sum, session) => sum + session.caloriesBurned, 0) + + cardioSessions.reduce((sum, session) => sum + session.caloriesBurned, 0); + + // Calculate streak (consecutive days with activity) + const currentStreak = calculateStreak([...workoutSessions, ...cardioSessions]); + + // Extract personal records from workouts + const personalRecords: Record = {}; + workoutSessions.forEach((session) => { + const maxWeight = Math.max(...session.sets.map((s) => s.weight)); + const key = `${session.exerciseName}_pr`; + if (!personalRecords[key] || maxWeight > personalRecords[key]) { + personalRecords[key] = maxWeight; + } + }); + + const metrics: UserMetrics = { + totalWorkouts, + totalCardioDistance, + totalCaloriesBurned, + currentStreak, + personalRecords, + lastUpdated: Date.now(), + }; + + localStorage.setItem(STORAGE_KEYS.USER_METRICS, JSON.stringify(metrics)); + } catch (error) { + console.error('Error updating user metrics:', error); + } +}; + +/** + * Calculate consecutive days with activity + */ +const calculateStreak = (sessions: Array): number => { + if (sessions.length === 0) return 0; + + const dates = sessions.map((session) => new Date(session.date).toDateString()).filter((date, index, self) => self.indexOf(date) === index).sort((a, b) => new Date(b).getTime() - new Date(a).getTime()); + + let streak = 1; + for (let i = 1; i < dates.length; i++) { + const currentDate = new Date(dates[i]); + const previousDate = new Date(dates[i - 1]); + const diffTime = Math.abs(previousDate.getTime() - currentDate.getTime()); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + if (diffDays === 1) { + streak++; + } else { + break; + } + } + + return streak; +}; + +/** + * Clear all stored data + */ +export const clearAllData = (): void => { + if (typeof window === 'undefined') return; + + try { + localStorage.removeItem(STORAGE_KEYS.WORKOUT_SESSIONS); + localStorage.removeItem(STORAGE_KEYS.CARDIO_SESSIONS); + localStorage.removeItem(STORAGE_KEYS.NUTRITION_LOGS); + localStorage.removeItem(STORAGE_KEYS.USER_METRICS); + } catch (error) { + console.error('Error clearing data:', error); + } +}; + +/** + * Export data as JSON for backup + */ +export const exportData = (): string => { + const data = { + workouts: getWorkoutSessions(), + cardio: getCardioSessions(), + nutrition: getNutritionLogs(), + metrics: getUserMetrics(), + exportDate: new Date().toISOString(), + }; + return JSON.stringify(data, null, 2); +}; + +/** + * Import data from JSON backup + */ +export const importData = (jsonData: string): boolean => { + if (typeof window === 'undefined') return false; + + try { + const data = JSON.parse(jsonData); + if (data.workouts) localStorage.setItem(STORAGE_KEYS.WORKOUT_SESSIONS, JSON.stringify(data.workouts)); + if (data.cardio) localStorage.setItem(STORAGE_KEYS.CARDIO_SESSIONS, JSON.stringify(data.cardio)); + if (data.nutrition) localStorage.setItem(STORAGE_KEYS.NUTRITION_LOGS, JSON.stringify(data.nutrition)); + if (data.metrics) localStorage.setItem(STORAGE_KEYS.USER_METRICS, JSON.stringify(data.metrics)); + return true; + } catch (error) { + console.error('Error importing data:', error); + return false; + } +};