diff --git a/mobile/components/CreateProgram.js b/mobile/components/CreateProgram.js index 618721a..32ddbfd 100644 --- a/mobile/components/CreateProgram.js +++ b/mobile/components/CreateProgram.js @@ -1,51 +1,241 @@ -import React, { useState } from 'react'; -import { StyleSheet, Text, View, TextInput, Button, FlatList, TouchableOpacity, KeyboardAvoidingView } from 'react-native'; +import React, { useState, useEffect} from 'react'; +import { ScrollView,StyleSheet, Text, View, TextInput, TouchableOpacity, Pressable, FlatList, Modal } from 'react-native'; +import { Picker } from '@react-native-picker/picker'; // Using Picker for the dropdown +import apiInstance from "../Api"; +import { useQuery } from "@tanstack/react-query"; +import { useSelector } from 'react-redux'; +import { userSessionToken } from '../user.js'; +import Toast from 'react-native-toast-message'; +import SpinboxInput from "./common/SpinboxInput"; -const CreateProgram = ({ darkMode, setSelectedPage }) => { +const CreateProgram = ({ darkMode }) => { const styles = darkMode ? darkStyles : lightStyles; const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); - const [labels, setLabels] = useState([]); - const [labelText, setLabelText] = useState(''); - const [exercises, setExercises] = useState([]); - const [exercise, setExercise] = useState({ name: '', repetitions: '', sets: '' }); - - const handleProgramCreation = () => { - const newProgram = { title, description, labels, exercises }; - console.log('Creating program:', newProgram); - // Add your logic to handle program submission + const [weeks, setWeeks] = useState([]); + const [exerciseModalVisible, setExerciseModalVisible] = useState(false); + const [selectedWeekIndex, setSelectedWeekIndex] = useState(null); + const [selectedWorkoutIndex, setSelectedWorkoutIndex] = useState(null); + const [selectedExercise, setSelectedExercise] = useState(''); + const [sets, setSets] = useState(''); + const [type, setType] = useState('BODY_BUILDING'); + const [level, setLevel] = useState('BEGINNER'); + const [interval, setInterval] = useState(0); + + const [reps, setReps] = useState(''); + + const SpinBoxComponent = () => { + + return ( + + + + ); }; + const [exerciseOptions, setExerciseOptions] = useState([]) - const addLabel = () => { - if (labelText.trim()) { - setLabels([...labels, labelText.trim()]); - setLabelText(''); - } + const { + data: exercisesData, + isFetching: exercisesIsFetching, + isLoading: exercisesIsLoading, + } = useQuery({ + queryKey: ['exercises'], + queryFn: async () => { + const response = await apiInstance(sessionToken).get('api/exercises') + + return response.data + }, + refetchOnWindowFocus: false, + }) + + useEffect(() => { + if (exercisesData && !exercisesIsFetching) { + setExerciseOptions(exercisesData) + } + }, [exercisesData, exercisesIsFetching]) + + const sessionToken = useSelector(userSessionToken) + + /*const exerciseOptions = [ + { id: 1, name: 'push-up' }, + { id: 2, name: 'pull-up' }, + ];*/ + const types = [ + { id: 0, label: 'Body Building', value:'BODY_BUILDING' } + + ]; + const levels = [ + { id: 0, label: 'Beginner', value:'BEGINNER' }, + { id: 1, label: 'Intermediate', value:'INTERMEDIATE' }, + { id: 2, label: 'Professional', value:'PROFESSIONAL' } + ]; + + // Add Week + const addWeek = () => { + setWeeks([...weeks, { workouts: [] }]); }; - const removeLabel = (index) => { - setLabels(labels.filter((_, i) => i !== index)); + // Remove Week + const removeWeek = (index) => { + setWeeks(weeks.filter((_, i) => i !== index)); }; + // Add Workout + const addWorkout = (weekIndex) => { + setWeeks((prevWeeks) => + prevWeeks.map((week, i) => + i === weekIndex + ? { + ...week, + workouts: [...week.workouts, { name: '', exercises: [] }], + } + : week + ) + ); + }; + + // Remove Workout + const removeWorkout = (weekIndex, workoutIndex) => { + const updatedWeeks = [...weeks]; + updatedWeeks[weekIndex].workouts = updatedWeeks[weekIndex].workouts.filter( + (_, i) => i !== workoutIndex + ); + setWeeks(updatedWeeks); + }; + + const clearFields = () => { + setTitle(''); + setDescription(''); + setWeeks([]); + setType(''); + setSelectedExercise(''); + setSets(''); + setReps(''); + }; + + const handleProgramCreation = async () => { + /*const newProgram = { title, description, labels, exercises }; + console.log('Creating program:', newProgram);*/ + console.log(title); + console.log(description); + console.log(type); + console.log(level); + console.log(interval); + weeks.forEach((week)=>{ + console.log("Week "+weeks.indexOf(week)); + console.log("Workout count: "+week.workouts.length); + week.workouts.forEach((workout)=>{ + console.log("Workout "+week.workouts.indexOf(workout)); + console.log("Workout name: "+workout.name); + console.log("Exercise count: "+workout.exercises.length); + workout.exercises.forEach((exercise)=>{ + console.log("Exercise "+exercise.exerciseId); + console.log("Sets: "+exercise.sets); + console.log("Reps: "+exercise.repetitions); + + }); + }); + }); + + if (!title || !description || weeks.length === 0 || weeks.some((item)=>{return item.workouts.length==0||item.workouts.some((workout)=>{return workout.name.length == 0 || workout.exercises.length==0})})) { + Toast.show({ + type: 'error', + position: 'bottom', + text1: 'Create Program Error', + text2: 'Fill all the fields to create a program.', + visibilityTime: 2000, + autoHide: true, + topOffset: 30, + bottomOffset: 40 + }); + return; + } + + const response = await apiInstance(sessionToken).post('api/training-programs', { + title, + description, + type, + level, + interval, + weeks + }); + + if (response.status === 201) { + Toast.show({ + type: 'success', + position: 'bottom', + text1: 'Program Created', + text2: 'Successfully created the program.', + visibilityTime: 2000, + autoHide: true, + topOffset: 30, + bottomOffset: 40 + }); + clearFields(); + } + else{ + Toast.show({ + type: 'error', + position: 'bottom', + text1: 'Create Program Error', + text2: 'There was an error while creating the program. Please try again.', + visibilityTime: 2000, + autoHide: true, + topOffset: 30, + bottomOffset: 40 + }); + return; + + } + }; + const handleWorkoutNameChange = (weekIndex, workoutIndex, newName) => { + setWeeks((prevWeeks) => + prevWeeks.map((week, i) => + i === weekIndex + ? { + ...week, + workouts: week.workouts.map((workout, j) => + j === workoutIndex ? { ...workout, name: newName } : workout + ), + } + : week + ) + ); + }; + // Add Exercise const addExercise = () => { - if (exercise.name.trim() && exercise.repetitions && exercise.sets) { - setExercises([...exercises, exercise]); - setExercise({ name: '', repetitions: '', sets: '' }); + if (selectedExercise && sets && reps) { + const updatedWeeks = [...weeks]; + const selectedExerciseId = exerciseOptions.find((element)=>element.name===selectedExercise).id; + updatedWeeks[selectedWeekIndex].workouts[selectedWorkoutIndex].exercises.push({ + exerciseId: selectedExerciseId, + sets, + repetitions:reps, + }); + setWeeks(updatedWeeks); + setSelectedExercise(''); + setSets(''); + setReps(''); + setExerciseModalVisible(false); } }; - const removeExercise = (index) => { - setExercises(exercises.filter((_, i) => i !== index)); + // Remove Exercise + const removeExercise = (weekIndex, workoutIndex, exerciseIndex) => { + const updatedWeeks = [...weeks]; + updatedWeeks[weekIndex].workouts[workoutIndex].exercises = updatedWeeks[ + weekIndex + ].workouts[workoutIndex].exercises.filter((_, i) => i !== exerciseIndex); + setWeeks(updatedWeeks); }; return ( - + Create New Program @@ -53,362 +243,326 @@ const CreateProgram = ({ darkMode, setSelectedPage }) => { - - - - Add - + setType(value)} + > + {types.map((item) => ( + + ))} + + + setLevel(value)} + > + {levels.map((item) => ( + + ))} + + + Select Interval + + index.toString()} - renderItem={({ item, index }) => ( - - {item} - removeLabel(index)}> - + data={weeks} + keyExtractor={(_, index) => index.toString()} + renderItem={({ item: week, index: weekIndex }) => ( + + Week {weekIndex + 1} + addWorkout(weekIndex)} + > + Add Workout - - )} - horizontal - showsHorizontalScrollIndicator={false} - /> - Exercises - - - setExercise({ ...exercise, name: text })} - /> - setExercise({ ...exercise, repetitions: text })} - /> - setExercise({ ...exercise, sets: text })} - /> - - Add - - + i.toString()} + renderItem={({ item: workout, index: workoutIndex }) => ( + + + Workout {workoutIndex + 1} + + handleWorkoutNameChange(weekIndex, workoutIndex, text)} + /> - index.toString()} - renderItem={({ item, index }) => ( - - - {item.name} - {item.repetitions} reps x {item.sets} sets - - removeExercise(index)}> - + { + setSelectedWeekIndex(weekIndex); + setSelectedWorkoutIndex(workoutIndex); + setExerciseModalVisible(true); + }} + > + Add Exercise + + + i.toString()} + renderItem={({ item: exercise, index: exerciseIndex }) => ( + + + {exerciseOptions.find((i)=>i.id===exercise.exerciseId).name} - {exercise.repetitions} reps x {exercise.sets}{' '} + sets + + + removeExercise( + weekIndex, + workoutIndex, + exerciseIndex + ) + } + > + + + + )} + /> + removeWorkout(weekIndex, workoutIndex)} + > + Remove Workout + + + )} + /> + removeWeek(weekIndex)}> + Remove Week )} /> + + + Add Week + + + + setExerciseModalVisible(false)} + > + + + Add Exercise + setSelectedExercise(value)} + > + + {exerciseOptions.map((exercise) => ( + + ))} + + + + + + + + + + Add + + + setExerciseModalVisible(false)}> + Cancel + + + + + - Create Program + Create Program - + ); }; -// Reusing similar styles from CreatePost component with additional styling for exercises const lightStyles = StyleSheet.create({ container: { - padding: 20, - backgroundColor: '#f5f5f5', - flex: 1, - }, - title: { - fontSize: 26, - fontWeight: 'bold', - color: '#333', - marginBottom: 15, - }, - input: { - height: 50, - backgroundColor: '#fff', - borderRadius: 10, - paddingHorizontal: 15, - marginBottom: 10, - fontSize: 16, - color: '#333', - shadowColor: '#000', - shadowOpacity: 0.1, - shadowRadius: 5, - elevation: 3, - }, - descriptionInput: { - height: 100, - textAlignVertical: 'top', - }, - labelContainer: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 10, - }, - labelInput: { - flex: 1, - height: 50, - backgroundColor: '#fff', - borderRadius: 10, - paddingHorizontal: 15, - fontSize: 16, - shadowColor: '#000', - shadowOpacity: 0.1, - shadowRadius: 5, - elevation: 3, - }, - addButton: { - marginLeft: 10, - paddingVertical: 12, - paddingHorizontal: 16, - backgroundColor: '#007bff', - borderRadius: 10, - elevation: 3, - }, - addButtonText: { - color: '#fff', - fontWeight: 'bold', - }, - labelItem: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: '#e0e0e0', - paddingHorizontal: 10, - paddingVertical: 5, - borderRadius: 15, - marginRight: 5, - }, - labelItemText: { - fontSize: 14, - color: '#333', - }, - removeLabel: { - color: '#ff3b30', - marginLeft: 5, - fontWeight: 'bold', - }, - sectionTitle: { - fontSize: 20, - fontWeight: 'bold', - marginVertical: 10, - color: '#333', - }, - exerciseContainer: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 10, - }, - exerciseInput: { - flex: 1, - height: 50, - backgroundColor: '#fff', - borderRadius: 10, - paddingHorizontal: 15, - fontSize: 16, - marginRight: 5, - shadowColor: '#000', - shadowOpacity: 0.1, - shadowRadius: 5, - elevation: 3, - }, - exerciseItem: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingVertical: 5, - backgroundColor: '#e0e0e0', - borderRadius: 10, - paddingHorizontal: 15, - marginVertical: 5, - }, - exerciseItemText: { - fontSize: 14, - color: '#333', - }, - postButton: { - backgroundColor: '#007bff', - paddingVertical: 15, - borderRadius: 10, - alignItems: 'center', - marginTop: 20, - shadowColor: '#007bff', - shadowOpacity: 0.5, - shadowRadius: 5, - elevation: 5, - }, - postButtonText: { - color: '#fff', - fontSize: 16, - fontWeight: 'bold', - }, - placeholderColor: 'gray', -}); + padding: 20, + backgroundColor: '#f5f5f5', + flex: 1, + }, + + container: { + padding: 10, + flex: 1, + }, + title: { + fontSize: 26, + fontWeight: 'bold', + color: '#333', + marginBottom: 15, + }, + input: { + height: 50, + backgroundColor: '#fff', + borderRadius: 10, + paddingHorizontal: 15, + marginBottom: 10, + fontSize: 16, + color: '#333', + shadowColor: '#000', + shadowOpacity: 0.1, + shadowRadius: 5, + elevation: 3, + }, + descriptionInput: { + height: 100, + textAlignVertical: 'top', + }, -const darkStyles = StyleSheet.create({ - container: { - padding: 20, - backgroundColor: '#121212', - flex: 1, - }, - title: { - fontSize: 26, - fontWeight: 'bold', - color: '#ffffff', - marginBottom: 15, - }, - input: { - height: 50, - backgroundColor: '#333333', - borderRadius: 10, - paddingHorizontal: 15, - marginBottom: 10, - fontSize: 16, - color: '#ffffff', - shadowColor: '#000', - shadowOpacity: 0.2, - shadowRadius: 5, - elevation: 3, - }, - descriptionInput: { - height: 100, - textAlignVertical: 'top', - }, - labelContainer: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 10, - }, - labelInput: { - flex: 1, - height: 50, - backgroundColor: '#333333', - borderRadius: 10, - paddingHorizontal: 15, - fontSize: 16, - color: '#ffffff', - shadowColor: '#000', - shadowOpacity: 0.2, - shadowRadius: 5, - elevation: 3, - }, addButton: { - marginLeft: 10, - paddingVertical: 12, - paddingHorizontal: 16, - backgroundColor: '#007bff', - borderRadius: 10, - elevation: 3, - }, - addButtonText: { - color: '#ffffff', - fontWeight: 'bold', - }, - labelItem: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: '#555555', - paddingHorizontal: 10, - paddingVertical: 5, - borderRadius: 15, - marginRight: 5, - }, - labelItemText: { - fontSize: 14, - color: '#ffffff', - }, - removeLabel: { - color: '#ff3b30', - marginLeft: 5, - fontWeight: 'bold', - }, - sectionTitle: { - fontSize: 20, - fontWeight: 'bold', - marginVertical: 10, - color: '#ffffff', - }, - exerciseContainer: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 10, - }, - exerciseInput: { - flex: 1, - height: 50, - backgroundColor: '#333333', - borderRadius: 10, - paddingHorizontal: 15, - fontSize: 16, - color: '#ffffff', - marginRight: 5, - shadowColor: '#000', - shadowOpacity: 0.2, - shadowRadius: 5, - elevation: 3, - }, - exerciseItem: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingVertical: 5, - backgroundColor: '#555555', - borderRadius: 10, - paddingHorizontal: 15, - marginVertical: 5, - }, - exerciseItemText: { - fontSize: 14, - color: '#ffffff', - }, - postButton: { - backgroundColor: '#007bff', - paddingVertical: 15, - borderRadius: 10, - alignItems: 'center', - marginTop: 20, - shadowColor: '#007bff', - shadowOpacity: 0.6, - shadowRadius: 5, - elevation: 5, - }, - postButtonText: { - color: '#ffffff', - fontSize: 16, - fontWeight: 'bold', - }, - placeholderColor: '#aaaaaa', + marginLeft: 10, + paddingVertical: 12, + paddingHorizontal: 16, + backgroundColor: '#007bff', + borderRadius: 10, + elevation: 3, + }, + addButtonText: { + color: '#fff', + fontWeight: 'bold', + }, + addWeekButton: { + marginLeft: 10, + paddingVertical: 12, + paddingHorizontal: 16, + backgroundColor: '#007bff', + borderRadius: 10, + alignItems: 'center', + width:100, + elevation: 3, + }, + addWeekButtonContainer: { + alignItems: 'center', + }, + postButton: { + backgroundColor: '#007bff', + paddingVertical: 15, + borderRadius: 10, + alignItems: 'center', + marginTop: 20, + shadowColor: '#007bff', + shadowOpacity: 0.5, + shadowRadius: 5, + elevation: 5, + }, + postButtonText: { + color: '#fff', + fontSize: 16, + fontWeight: 'bold', + }, + weekContainer: { marginBottom: 20 }, + workoutContainer: { marginBottom: 15 }, + sectionTitle: { fontSize: 18 }, + exerciseContainer: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 10, + }, + exerciseInput: { + flex: 1, + height: 50, + backgroundColor: '#fff', + borderRadius: 10, + paddingHorizontal: 15, + fontSize: 16, + marginRight: 5, + shadowColor: '#000', + shadowOpacity: 0.1, + shadowRadius: 5, + elevation: 3, + }, + exerciseItem: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: 5, + backgroundColor: '#e0e0e0', + borderRadius: 10, + paddingHorizontal: 15, + marginVertical: 5, + }, + exerciseItemText: { + fontSize: 14, + color: '#333', + }, + removeLabel: { color: 'red' }, + modalContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + modalContent: { + backgroundColor: '#fff', + borderRadius: 10, + padding: 20, + width: '90%', + alignItems: 'center', + }, + modalTitle: { + fontSize: 20, + fontWeight: 'bold', + marginBottom: 15, + }, + placeholderColor: 'gray', + picker: { + width: '100%', + height: 50, + backgroundColor: '#f0f0f0', + marginBottom: 15, + borderRadius: 5, + }, + modalButtons: { + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + }, + cancelButton: { + backgroundColor: '#ff3b30', + paddingVertical: 12, + paddingHorizontal: 16, + borderRadius: 10, + }, + cancelButtonText: { + color: '#fff', + fontWeight: 'bold', + }, }); -// Define darkStyles similarly as lightStyles with color changes for dark mode - export default CreateProgram; diff --git a/mobile/components/common/SpinboxInput.js b/mobile/components/common/SpinboxInput.js new file mode 100644 index 0000000..8f769c7 --- /dev/null +++ b/mobile/components/common/SpinboxInput.js @@ -0,0 +1,51 @@ +import React, { useState } from 'react'; +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; + +const SpinboxInput = ({onChange}) => { + const [value, setValue] = useState(0); + + const handleIncrement = () => { +const newValue = Math.min(value + 1, 2); + setValue(newValue); + onChange(newValue); }; + + const handleDecrement = () => { +const newValue = Math.max(value - 1, 0); + setValue(newValue); + onChange(newValue); }; + + return ( + + + - + + {value} + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 1, + borderColor: '#ccc', + borderRadius: 5, + paddingHorizontal: 10, + }, + button: { + padding: 8, + }, + buttonText: { + fontSize: 20, + }, + value: { + fontSize: 18, + marginHorizontal: 10, + }, +}); + +export default SpinboxInput; \ No newline at end of file