Add src/app/lib/storage/workoutStorage.ts

This commit is contained in:
2026-03-11 19:46:32 +00:00
parent 2735e28f7f
commit f71a9cb2f3

View File

@@ -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<string, number>;
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<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;
}
});
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<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;
}
};