POWR/types/workout.ts

266 lines
6.2 KiB
TypeScript
Raw Normal View History

2025-02-24 22:27:01 -05:00
// types/workout.ts
import type { WorkoutTemplate, TemplateType } from './templates';
import type { BaseExercise } from './exercise';
import type { SyncableContent } from './shared';
import type { NostrEvent } from './nostr';
import { generateId } from '@/utils/ids';
2025-02-24 22:27:01 -05:00
/**
* Core workout status types
*/
export type WorkoutStatus = 'idle' | 'active' | 'paused' | 'completed';
/**
* Individual workout set
*/
export interface WorkoutSet {
id: string;
weight?: number;
reps?: number;
rpe?: number;
type: 'warmup' | 'normal' | 'drop' | 'failure';
isCompleted: boolean;
notes?: string;
timestamp?: number;
lastUpdated?: number;
completedAt?: number;
duration?: number;
2025-02-24 22:27:01 -05:00
}
/**
* Exercise within a workout
*/
export interface WorkoutExercise extends BaseExercise {
exerciseId?: string;
2025-02-24 22:27:01 -05:00
sets: WorkoutSet[];
targetSets?: number;
targetReps?: number;
notes?: string;
restTime?: number; // Rest time in seconds
isCompleted?: boolean;
lastUpdated?: number;
}
/**
* Active workout tracking
*/
export interface Workout extends SyncableContent {
id: string;
title: string;
type: TemplateType;
exercises: WorkoutExercise[];
startTime: number;
endTime?: number;
notes?: string;
lastUpdated?: number;
tags?: string[];
// Template reference if workout was started from template
templateId?: string; // Keep only one templateId property
templatePubkey?: string; // Add this for template references
// Add shareStatus property
shareStatus?: 'local' | 'public' | 'limited';
2025-02-24 22:27:01 -05:00
// Workout configuration
rounds?: number;
duration?: number;
interval?: number;
2025-02-24 22:27:01 -05:00
restBetweenRounds?: number;
// Workout metrics
totalVolume?: number;
totalReps?: number;
averageRpe?: number;
// Completion tracking
isCompleted: boolean;
roundsCompleted?: number;
exercisesCompleted?: number;
// For Nostr integration
nostrEventId?: string;
}
2025-03-07 10:09:55 -05:00
/**
* Options for completing a workout
*/
export interface WorkoutCompletionOptions {
// Storage option
storageType: 'local_only' | 'publish_complete' | 'publish_limited';
// Social sharing option
shareOnSocial: boolean;
socialMessage?: string;
// Template update options
templateAction: 'keep_original' | 'update_existing' | 'save_as_new';
newTemplateName?: string;
}
2025-02-24 22:27:01 -05:00
/**
* Personal Records
*/
export interface PersonalRecord {
id: string;
exerciseId: string;
metric: 'weight' | 'reps' | 'volume' | 'time';
value: number;
workoutId: string;
achievedAt: number;
// Context about the PR
exercise: {
title: string;
equipment?: string;
};
previousValue?: number;
notes?: string;
}
/**
* Workout Summary Statistics
*/
export interface WorkoutSummary {
id: string;
title: string;
type: TemplateType;
duration: number; // Total time in milliseconds
startTime: number;
endTime: number;
// Overall stats
exerciseCount: number;
completedExercises: number;
totalVolume: number;
totalReps: number;
averageRpe?: number;
// Exercise-specific summaries
exerciseSummaries: Array<{
exerciseId: string;
title: string;
setCount: number;
completedSets: number;
volume: number;
peakWeight?: number;
totalReps: number;
averageRpe?: number;
}>;
// Achievements
personalRecords: PersonalRecord[];
}
/**
* Rest Timer State
*/
export interface RestTimer {
isActive: boolean;
duration: number; // Total rest duration in seconds
remaining: number; // Remaining time in seconds
exerciseId?: string; // Associated exercise if any
setIndex?: number; // Associated set if any
}
/**
* Global Workout State
*/
export interface WorkoutState {
status: WorkoutStatus;
activeWorkout: Workout | null;
currentExerciseIndex: number;
currentSetIndex: number;
elapsedTime: number; // Total workout time in milliseconds
lastSaved?: number; // Timestamp of last save
restTimer: RestTimer;
}
/**
* Workout Actions
*/
export type WorkoutAction =
| { type: 'START_WORKOUT'; payload: Partial<Workout> }
| { type: 'PAUSE_WORKOUT' }
| { type: 'RESUME_WORKOUT' }
| { type: 'COMPLETE_WORKOUT' }
| { type: 'UPDATE_SET'; payload: { exerciseIndex: number; setIndex: number; data: Partial<WorkoutSet> } }
| { type: 'NEXT_EXERCISE' }
| { type: 'PREVIOUS_EXERCISE' }
| { type: 'START_REST'; payload: number }
| { type: 'STOP_REST' }
| { type: 'TICK'; payload: number }
| { type: 'RESET' };
/**
* Helper functions
*/
/**
* Converts a template to an active workout
*/
export function templateToWorkout(template: WorkoutTemplate): Workout {
return {
id: generateId('nostr'),
2025-02-24 22:27:01 -05:00
title: template.title,
type: template.type,
exercises: template.exercises.map(ex => ({
...ex.exercise,
sets: Array(ex.targetSets).fill({
id: generateId('nostr'),
2025-02-24 22:27:01 -05:00
type: 'normal',
reps: ex.targetReps,
isCompleted: false
}),
targetSets: ex.targetSets,
targetReps: ex.targetReps,
notes: ex.notes
})),
templateId: template.id,
startTime: Date.now(),
isCompleted: false,
rounds: template.rounds,
duration: template.duration,
interval: template.interval,
restBetweenRounds: template.restBetweenRounds,
created_at: Date.now(),
availability: {
source: ['local']
}
};
}
/**
* Creates a Nostr workout record event
*/
export function createNostrWorkoutEvent(workout: Workout): NostrEvent {
const exerciseTags = workout.exercises.flatMap(exercise =>
exercise.sets.map(set => [
'exercise',
`33401:${exercise.id}`,
set.weight?.toString() || '',
set.reps?.toString() || '',
set.rpe?.toString() || '',
set.type
])
);
const workoutTags = workout.tags ? workout.tags.map(tag => ['t', tag]) : [];
return {
kind: 33403,
content: workout.notes || '',
tags: [
['d', workout.id],
['title', workout.title],
['type', workout.type],
['start', Math.floor(workout.startTime / 1000).toString()],
['end', Math.floor(workout.endTime! / 1000).toString()],
['completed', workout.isCompleted.toString()],
...exerciseTags,
...workoutTags
],
created_at: Math.floor(Date.now() / 1000)
};
}