context and goals definition added
This commit is contained in:
1 parent
28b5b52a8f
commit
803c205994
10 files changed
+1073
-170
No files matched your search
@@ -0,0 +1,414 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
Modal,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
ScrollView,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import DateTimePicker from '@react-native-community/datetimepicker';
|
||||
import type { CreateGoalData } from '../services/fitnessGoals';
|
||||
|
||||
interface GoalCreationModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (goalData: CreateGoalData) => Promise<void>;
|
||||
}
|
||||
|
||||
const GOAL_TYPES = [
|
||||
{ value: 'weight_target', label: 'Weight Target' },
|
||||
{ value: 'strength_milestone', label: 'Strength Milestone' },
|
||||
{ value: 'endurance_target', label: 'Endurance Target' },
|
||||
{ value: 'flexibility_goal', label: 'Flexibility Goal' },
|
||||
{ value: 'habit_building', label: 'Habit Building' },
|
||||
{ value: 'custom', label: 'Custom Goal' },
|
||||
] as const;
|
||||
|
||||
const PRIORITIES = [
|
||||
{ value: 'low', label: 'Low', color: '#10b981' },
|
||||
{ value: 'medium', label: 'Medium', color: '#f59e0b' },
|
||||
{ value: 'high', label: 'High', color: '#ef4444' },
|
||||
] as const;
|
||||
|
||||
export function GoalCreationModal({ visible, onClose, onSubmit }: GoalCreationModalProps) {
|
||||
const [goalType, setGoalType] = useState<CreateGoalData['goalType']>('weight_target');
|
||||
const [title, setTitle] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [targetValue, setTargetValue] = useState('');
|
||||
const [currentValue, setCurrentValue] = useState('');
|
||||
const [unit, setUnit] = useState('');
|
||||
const [priority, setPriority] = useState<'low' | 'medium' | 'high'>('medium');
|
||||
const [targetDate, setTargetDate] = useState<Date | undefined>();
|
||||
const [showDatePicker, setShowDatePicker] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const resetForm = () => {
|
||||
setGoalType('weight_target');
|
||||
setTitle('');
|
||||
setDescription('');
|
||||
setTargetValue('');
|
||||
setCurrentValue('');
|
||||
setUnit('');
|
||||
setPriority('medium');
|
||||
setTargetDate(undefined);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!title.trim()) {
|
||||
alert('Please enter a goal title');
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const goalData: CreateGoalData = {
|
||||
goalType,
|
||||
title: title.trim(),
|
||||
description: description.trim() || undefined,
|
||||
targetValue: targetValue ? parseFloat(targetValue) : undefined,
|
||||
currentValue: currentValue ? parseFloat(currentValue) : undefined,
|
||||
unit: unit.trim() || undefined,
|
||||
targetDate: targetDate?.toISOString(),
|
||||
priority,
|
||||
};
|
||||
|
||||
await onSubmit(goalData);
|
||||
resetForm();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error('Error creating goal:', error);
|
||||
alert('Failed to create goal. Please try again.');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
resetForm();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
animationType="slide"
|
||||
presentationStyle="pageSheet"
|
||||
onRequestClose={handleClose}
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.headerTitle}>Create Fitness Goal</Text>
|
||||
<TouchableOpacity onPress={handleClose} style={styles.closeButton}>
|
||||
<Ionicons name="close" size={28} color="#111827" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
|
||||
{/* Goal Type */}
|
||||
<View style={styles.field}>
|
||||
<Text style={styles.label}>Goal Type *</Text>
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.typeScroll}>
|
||||
{GOAL_TYPES.map((type) => (
|
||||
<TouchableOpacity
|
||||
key={type.value}
|
||||
style={[
|
||||
styles.typeButton,
|
||||
goalType === type.value && styles.typeButtonActive,
|
||||
]}
|
||||
onPress={() => setGoalType(type.value)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.typeButtonText,
|
||||
goalType === type.value && styles.typeButtonTextActive,
|
||||
]}
|
||||
>
|
||||
{type.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* Title */}
|
||||
<View style={styles.field}>
|
||||
<Text style={styles.label}>Title *</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={title}
|
||||
onChangeText={setTitle}
|
||||
placeholder="e.g., Lose 5kg"
|
||||
placeholderTextColor="#9ca3af"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Description */}
|
||||
<View style={styles.field}>
|
||||
<Text style={styles.label}>Description</Text>
|
||||
<TextInput
|
||||
style={[styles.input, styles.textArea]}
|
||||
value={description}
|
||||
onChangeText={setDescription}
|
||||
placeholder="Optional description"
|
||||
placeholderTextColor="#9ca3af"
|
||||
multiline
|
||||
numberOfLines={3}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Target Value & Unit */}
|
||||
<View style={styles.row}>
|
||||
<View style={[styles.field, styles.flex1]}>
|
||||
<Text style={styles.label}>Target Value</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={targetValue}
|
||||
onChangeText={setTargetValue}
|
||||
placeholder="e.g., 70"
|
||||
placeholderTextColor="#9ca3af"
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.field, styles.flex1]}>
|
||||
<Text style={styles.label}>Unit</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={unit}
|
||||
onChangeText={setUnit}
|
||||
placeholder="e.g., kg"
|
||||
placeholderTextColor="#9ca3af"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Current Value */}
|
||||
<View style={styles.field}>
|
||||
<Text style={styles.label}>Current Value</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
value={currentValue}
|
||||
onChangeText={setCurrentValue}
|
||||
placeholder="Starting value (optional)"
|
||||
placeholderTextColor="#9ca3af"
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Target Date */}
|
||||
<View style={styles.field}>
|
||||
<Text style={styles.label}>Target Date</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.dateButton}
|
||||
onPress={() => setShowDatePicker(true)}
|
||||
>
|
||||
<Text style={targetDate ? styles.dateText : styles.datePlaceholder}>
|
||||
{targetDate ? targetDate.toLocaleDateString() : 'Select target date'}
|
||||
</Text>
|
||||
<Ionicons name="calendar-outline" size={20} color="#6b7280" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{showDatePicker && (
|
||||
<DateTimePicker
|
||||
value={targetDate || new Date()}
|
||||
mode="date"
|
||||
display={Platform.OS === 'ios' ? 'spinner' : 'default'}
|
||||
onChange={(event, selectedDate) => {
|
||||
setShowDatePicker(Platform.OS === 'ios');
|
||||
if (selectedDate) {
|
||||
setTargetDate(selectedDate);
|
||||
}
|
||||
}}
|
||||
minimumDate={new Date()}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Priority */}
|
||||
<View style={styles.field}>
|
||||
<Text style={styles.label}>Priority</Text>
|
||||
<View style={styles.priorityContainer}>
|
||||
{PRIORITIES.map((p) => (
|
||||
<TouchableOpacity
|
||||
key={p.value}
|
||||
style={[
|
||||
styles.priorityButton,
|
||||
priority === p.value && { backgroundColor: p.color },
|
||||
]}
|
||||
onPress={() => setPriority(p.value)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.priorityButtonText,
|
||||
priority === p.value && styles.priorityButtonTextActive,
|
||||
]}
|
||||
>
|
||||
{p.label}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.footer}>
|
||||
<TouchableOpacity
|
||||
style={[styles.submitButton, submitting && styles.submitButtonDisabled]}
|
||||
onPress={handleSubmit}
|
||||
disabled={submitting}
|
||||
>
|
||||
<Text style={styles.submitButtonText}>
|
||||
{submitting ? 'Creating...' : 'Create Goal'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f9fafb',
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
paddingTop: Platform.OS === 'ios' ? 60 : 20,
|
||||
backgroundColor: '#fff',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#e5e7eb',
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: '600',
|
||||
color: '#111827',
|
||||
},
|
||||
closeButton: {
|
||||
padding: 4,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
},
|
||||
field: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
label: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#374151',
|
||||
marginBottom: 8,
|
||||
},
|
||||
input: {
|
||||
backgroundColor: '#fff',
|
||||
borderWidth: 1,
|
||||
borderColor: '#d1d5db',
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
fontSize: 16,
|
||||
color: '#111827',
|
||||
},
|
||||
textArea: {
|
||||
height: 80,
|
||||
textAlignVertical: 'top',
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
flex1: {
|
||||
flex: 1,
|
||||
},
|
||||
typeScroll: {
|
||||
flexGrow: 0,
|
||||
},
|
||||
typeButton: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: '#d1d5db',
|
||||
backgroundColor: '#fff',
|
||||
marginRight: 8,
|
||||
},
|
||||
typeButtonActive: {
|
||||
backgroundColor: '#2563eb',
|
||||
borderColor: '#2563eb',
|
||||
},
|
||||
typeButtonText: {
|
||||
fontSize: 14,
|
||||
color: '#374151',
|
||||
},
|
||||
typeButtonTextActive: {
|
||||
color: '#fff',
|
||||
fontWeight: '600',
|
||||
},
|
||||
dateButton: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff',
|
||||
borderWidth: 1,
|
||||
borderColor: '#d1d5db',
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
},
|
||||
dateText: {
|
||||
fontSize: 16,
|
||||
color: '#111827',
|
||||
},
|
||||
datePlaceholder: {
|
||||
fontSize: 16,
|
||||
color: '#9ca3af',
|
||||
},
|
||||
priorityContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
priorityButton: {
|
||||
flex: 1,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: '#d1d5db',
|
||||
backgroundColor: '#fff',
|
||||
alignItems: 'center',
|
||||
},
|
||||
priorityButtonText: {
|
||||
fontSize: 14,
|
||||
color: '#374151',
|
||||
fontWeight: '500',
|
||||
},
|
||||
priorityButtonTextActive: {
|
||||
color: '#fff',
|
||||
fontWeight: '600',
|
||||
},
|
||||
footer: {
|
||||
padding: 20,
|
||||
paddingBottom: Platform.OS === 'ios' ? 40 : 20,
|
||||
backgroundColor: '#fff',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#e5e7eb',
|
||||
},
|
||||
submitButton: {
|
||||
backgroundColor: '#2563eb',
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
submitButtonDisabled: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
submitButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#fff',
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user