Update src/app/lib/storage/workoutStorage.ts
This commit is contained in:
@@ -1,309 +1,99 @@
|
||||
/**
|
||||
* Workout and metrics data persistence layer
|
||||
* Handles saving and retrieving workout data from localStorage
|
||||
*/
|
||||
|
||||
export interface WorkoutSet {
|
||||
reps: number;
|
||||
weight: number;
|
||||
timestamp: number;
|
||||
}
|
||||
// This file contains workout storage utilities
|
||||
|
||||
export interface WorkoutSession {
|
||||
id: string;
|
||||
exerciseName: string;
|
||||
date: string;
|
||||
sets: WorkoutSet[];
|
||||
duration: number; // in seconds
|
||||
caloriesBurned: number;
|
||||
notes?: string;
|
||||
duration: number;
|
||||
exercises: Exercise[];
|
||||
totalCalories: number;
|
||||
}
|
||||
|
||||
export interface CardioSession {
|
||||
export interface Exercise {
|
||||
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;
|
||||
name: string;
|
||||
sets: number;
|
||||
reps: number;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
export interface NutritionLog {
|
||||
id: string;
|
||||
date: string;
|
||||
meals: Meal[];
|
||||
totalCalories: number;
|
||||
}
|
||||
|
||||
export interface Meal {
|
||||
id: string;
|
||||
name: string;
|
||||
calories: number;
|
||||
protein: number;
|
||||
carbs: number;
|
||||
fats: number;
|
||||
meals: string[];
|
||||
macros: {
|
||||
protein: number;
|
||||
carbs: number;
|
||||
fats: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UserMetrics {
|
||||
totalWorkouts: number;
|
||||
totalCardioDistance: number;
|
||||
totalCaloriesBurned: number;
|
||||
currentStreak: number;
|
||||
personalRecords: Record<string, number>;
|
||||
lastUpdated: number;
|
||||
}
|
||||
export const workoutStorage = {
|
||||
// Store workout session
|
||||
saveWorkout: (session: WorkoutSession): void => {
|
||||
const workouts = JSON.parse(localStorage.getItem('workouts') || '[]');
|
||||
workouts.push(session);
|
||||
localStorage.setItem('workouts', JSON.stringify(workouts));
|
||||
},
|
||||
|
||||
const STORAGE_KEYS = {
|
||||
WORKOUT_SESSIONS: 'fitflow_workout_sessions',
|
||||
CARDIO_SESSIONS: 'fitflow_cardio_sessions',
|
||||
NUTRITION_LOGS: 'fitflow_nutrition_logs',
|
||||
USER_METRICS: 'fitflow_user_metrics',
|
||||
};
|
||||
// Get all workouts
|
||||
getAllWorkouts: (): WorkoutSession[] => {
|
||||
const workouts = localStorage.getItem('workouts');
|
||||
return workouts ? JSON.parse(workouts) : [];
|
||||
},
|
||||
|
||||
/**
|
||||
* 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<string, number> = {};
|
||||
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;
|
||||
}
|
||||
// Get workouts by date range
|
||||
getWorkoutsByDateRange: (startDate: Date, endDate: Date): WorkoutSession[] => {
|
||||
const allWorkouts = workoutStorage.getAllWorkouts();
|
||||
return allWorkouts.filter(w => {
|
||||
const workoutDate = new Date(w.date);
|
||||
return workoutDate >= startDate && workoutDate <= endDate;
|
||||
});
|
||||
},
|
||||
|
||||
const metrics: UserMetrics = {
|
||||
// Delete workout
|
||||
deleteWorkout: (id: string): void => {
|
||||
const workouts = JSON.parse(localStorage.getItem('workouts') || '[]');
|
||||
const filtered = workouts.filter((w: WorkoutSession) => w.id !== id);
|
||||
localStorage.setItem('workouts', JSON.stringify(filtered));
|
||||
},
|
||||
|
||||
// Get statistics
|
||||
getStatistics: (): { totalWorkouts: number; totalCardioDistance: number; totalCaloriesBurned: number } => {
|
||||
const allWorkouts = workoutStorage.getAllWorkouts();
|
||||
const totalWorkouts = allWorkouts.length;
|
||||
const totalCardioDistance = allWorkouts.reduce((sum, w) => sum + (w.duration * 0.15), 0);
|
||||
const totalCaloriesBurned = allWorkouts.reduce((sum, w) => sum + w.totalCalories, 0);
|
||||
|
||||
return {
|
||||
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<WorkoutSession | CardioSession>): 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;
|
||||
}
|
||||
// Save nutrition log
|
||||
saveNutritionLog: (log: NutritionLog): void => {
|
||||
const logs = JSON.parse(localStorage.getItem('nutritionLogs') || '[]');
|
||||
logs.push(log);
|
||||
localStorage.setItem('nutritionLogs', JSON.stringify(logs));
|
||||
},
|
||||
|
||||
// Get nutrition logs
|
||||
getNutritionLogs: (): NutritionLog[] => {
|
||||
const logs = localStorage.getItem('nutritionLogs');
|
||||
return logs ? JSON.parse(logs) : [];
|
||||
},
|
||||
|
||||
// Clear all data
|
||||
clearAll: (): void => {
|
||||
localStorage.removeItem('workouts');
|
||||
localStorage.removeItem('nutritionLogs');
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user