mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-23 01:01:27 +00:00
175 lines
5.2 KiB
TypeScript
175 lines
5.2 KiB
TypeScript
// components/workout/SetInput.tsx
|
||
import React, { useState, useCallback } from 'react';
|
||
import { View, TextInput, TouchableOpacity } from 'react-native';
|
||
import { Text } from '@/components/ui/text';
|
||
import { Circle, CheckCircle } from 'lucide-react-native'; // Lucide React icons
|
||
import { cn } from '@/lib/utils';
|
||
import { useWorkoutStore } from '@/stores/workoutStore';
|
||
import { useColorScheme } from '@/lib/theme/useColorScheme';
|
||
import type { WorkoutSet } from '@/types/workout';
|
||
import debounce from 'lodash/debounce';
|
||
|
||
interface SetInputProps {
|
||
exerciseIndex: number;
|
||
setIndex: number;
|
||
setNumber: number;
|
||
weight?: number;
|
||
reps?: number;
|
||
isCompleted?: boolean;
|
||
previousSet?: WorkoutSet;
|
||
}
|
||
|
||
export default function SetInput({
|
||
exerciseIndex,
|
||
setIndex,
|
||
setNumber,
|
||
weight = 0,
|
||
reps = 0,
|
||
isCompleted = false,
|
||
previousSet
|
||
}: SetInputProps) {
|
||
// Get theme colors
|
||
const { isDarkColorScheme } = useColorScheme();
|
||
|
||
// Local state for controlled inputs
|
||
const [weightValue, setWeightValue] = useState(weight.toString());
|
||
const [repsValue, setRepsValue] = useState(reps.toString());
|
||
|
||
// Get actions from store
|
||
const { updateSet, completeSet } = useWorkoutStore.getState();
|
||
|
||
// Debounced update functions to prevent too many state updates
|
||
const debouncedUpdateWeight = useCallback(
|
||
debounce((value: number) => {
|
||
updateSet(exerciseIndex, setIndex, { weight: value });
|
||
}, 500),
|
||
[exerciseIndex, setIndex]
|
||
);
|
||
|
||
const debouncedUpdateReps = useCallback(
|
||
debounce((value: number) => {
|
||
updateSet(exerciseIndex, setIndex, { reps: value });
|
||
}, 500),
|
||
[exerciseIndex, setIndex]
|
||
);
|
||
|
||
const handleWeightChange = (value: string) => {
|
||
if (value === '' || /^\d*\.?\d*$/.test(value)) {
|
||
setWeightValue(value);
|
||
const numValue = parseFloat(value);
|
||
if (!isNaN(numValue)) {
|
||
debouncedUpdateWeight(numValue);
|
||
}
|
||
}
|
||
};
|
||
|
||
const handleRepsChange = (value: string) => {
|
||
if (value === '' || /^\d*$/.test(value)) {
|
||
setRepsValue(value);
|
||
const numValue = parseInt(value, 10);
|
||
if (!isNaN(numValue)) {
|
||
debouncedUpdateReps(numValue);
|
||
}
|
||
}
|
||
};
|
||
|
||
const handleCompleteSet = useCallback(() => {
|
||
completeSet(exerciseIndex, setIndex);
|
||
}, [exerciseIndex, setIndex, completeSet]);
|
||
|
||
const handleCopyPreviousWeight = useCallback(() => {
|
||
if (previousSet?.weight) {
|
||
handleWeightChange(previousSet.weight.toString());
|
||
}
|
||
}, [previousSet]);
|
||
|
||
const handleCopyPreviousReps = useCallback(() => {
|
||
if (previousSet?.reps) {
|
||
handleRepsChange(previousSet.reps.toString());
|
||
}
|
||
}, [previousSet]);
|
||
|
||
// Get the appropriate colors based on theme variables
|
||
const purpleColor = 'hsl(261, 90%, 66%)'; // --purple from your constants
|
||
const mutedForegroundColor = isDarkColorScheme
|
||
? 'hsl(240, 5%, 64.9%)' // --muted-foreground dark
|
||
: 'hsl(240, 3.8%, 46.1%)'; // --muted-foreground light
|
||
|
||
return (
|
||
<View className={cn(
|
||
"flex-row items-center px-4 py-1 border-b border-border",
|
||
isCompleted && "bg-primary/5"
|
||
)}>
|
||
{/* Set Number */}
|
||
<Text className="w-8 text-sm font-medium text-muted-foreground text-center">
|
||
{setNumber}
|
||
</Text>
|
||
|
||
{/* Previous Set */}
|
||
<Text className="w-20 text-sm text-center text-muted-foreground">
|
||
{previousSet ? `${previousSet.weight}×${previousSet.reps}` : '—'}
|
||
</Text>
|
||
|
||
{/* Weight Input */}
|
||
<TouchableOpacity
|
||
className="flex-1 mx-1"
|
||
activeOpacity={0.7}
|
||
onLongPress={handleCopyPreviousWeight}
|
||
>
|
||
<TextInput
|
||
className={cn(
|
||
"h-8 px-3 rounded-md text-center text-foreground",
|
||
"bg-secondary border border-border",
|
||
isCompleted && "bg-primary/10 border-primary/20"
|
||
)}
|
||
value={weightValue === '0' ? '' : weightValue}
|
||
onChangeText={handleWeightChange}
|
||
keyboardType="decimal-pad"
|
||
placeholder="0"
|
||
placeholderTextColor={mutedForegroundColor}
|
||
selectTextOnFocus
|
||
/>
|
||
</TouchableOpacity>
|
||
|
||
{/* Reps Input */}
|
||
<TouchableOpacity
|
||
className="flex-1 mx-1"
|
||
activeOpacity={0.7}
|
||
onLongPress={handleCopyPreviousReps}
|
||
>
|
||
<TextInput
|
||
className={cn(
|
||
"h-8 px-3 rounded-md text-center text-foreground",
|
||
"bg-secondary border border-border",
|
||
isCompleted && "bg-primary/10 border-primary/20"
|
||
)}
|
||
value={repsValue === '0' ? '' : repsValue}
|
||
onChangeText={handleRepsChange}
|
||
keyboardType="number-pad"
|
||
placeholder="0"
|
||
placeholderTextColor={mutedForegroundColor}
|
||
selectTextOnFocus
|
||
/>
|
||
</TouchableOpacity>
|
||
|
||
{/* Complete Button using Lucide React icons - without fill */}
|
||
<TouchableOpacity
|
||
className="w-10 h-10 items-center justify-center"
|
||
onPress={handleCompleteSet}
|
||
>
|
||
{isCompleted ? (
|
||
<CheckCircle
|
||
size={24}
|
||
color={purpleColor}
|
||
// Removed fill and fillOpacity properties
|
||
/>
|
||
) : (
|
||
<Circle
|
||
size={24}
|
||
color={mutedForegroundColor}
|
||
/>
|
||
)}
|
||
</TouchableOpacity>
|
||
</View>
|
||
);
|
||
} |