diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx index c7820fa..0f87f10 100644 --- a/src/AppRoutes.tsx +++ b/src/AppRoutes.tsx @@ -1,24 +1,23 @@ -import { Switch, Route, Redirect } from "react-router-dom"; +import { Switch, Route, Redirect } from "react-router-dom" -import Welcome from "./pages/Welcome"; -import Login from "./pages/Login"; -import Home from "./pages/Home"; -import ForgotPasword from "./pages/ForgotPassword"; -import { Courses } from "./pages/Courses"; -import { CourseExercises } from "./pages/CourseExercises"; -import { Exercise } from "./pages/Exercise"; -import Settings from "./pages/Settings"; -import CourseComplete from "./pages/CourseComplete"; +import Welcome from "./pages/Welcome" +import Login from "./pages/Login" +import Home from "./pages/Home" +import ForgotPasword from "./pages/ForgotPassword" +import { Courses } from "./pages/Courses" +import { CourseExercises } from "./pages/CourseExercises" +import { Exercise } from "./pages/Exercise" -import { getRouteWelcome } from "./shared/consts/router"; -import { getRouteLogin } from "./shared/consts/router"; -import { getRouteHome } from "./shared/consts/router"; -import { getRouteForgotPassword } from "./shared/consts/router"; -import { getRouteCourses } from "./shared/consts/router"; -import { getRouteCourseExercises } from "./shared/consts/router"; -// import { getRouteExercise } from "./shared/consts/router"; -import { getRouteSettings } from "./shared/consts/router"; -import { getRouteCourseComplete } from "./shared/consts/router"; +import Settings from "./pages/Settings" +import CourseComplete from "./pages/CourseComplete" + +import { getRouteWelcome } from "./shared/consts/router" +import { getRouteLogin } from "./shared/consts/router" +import { getRouteHome } from "./shared/consts/router" +import { getRouteForgotPassword } from "./shared/consts/router" +import { getRouteCourses } from "./shared/consts/router" +import { getRouteSettings } from "./shared/consts/router" +import { getRouteCourseComplete } from "./shared/consts/router" const AppRoutes = () => ( @@ -30,18 +29,12 @@ const AppRoutes = () => ( - - - {/* */} - - - - - {/* */} - + + + -); +) -export default AppRoutes; \ No newline at end of file +export default AppRoutes diff --git a/src/confconnect.js b/src/confconnect.js index 9962955..d872a46 100644 --- a/src/confconnect.js +++ b/src/confconnect.js @@ -1,41 +1,36 @@ -import axios from 'axios'; - -import { AUTH_TOKEN, USER_KEY } from '@/shared/consts/localStorage'; -import { getRouteLogin } from '@/shared/consts/router'; +import axios from "axios" const instance = axios.create({ - baseURL: 'http://localhost:8093/', - headers: { - 'Content-type': 'application/json', - }, -}); + baseURL: "http://localhost:8093/", + headers: { + "Content-type": "application/json", + }, +}) instance.interceptors.response.use(undefined, async (error) => { - if (error.response?.status === 401) { - localStorage.removeItem(AUTH_TOKEN); - localStorage.removeItem(USER_KEY); + if (error.response?.status === 401) { + localStorage.removeItem("authToken") + localStorage.removeItem("token") + localStorage.removeItem("userId") + localStorage.removeItem("userName") - // перехватывает запросы и брал пути из функции + window.location.href = "/login" + } - window.location.href = getRouteLogin(); - } - - return Promise.reject(error); -}); + return Promise.reject(error) +}) instance.interceptors.request.use( - (config) => { - const token = localStorage.getItem(AUTH_TOKEN); - const c = config; - - if (token && config) { - c.headers.Authorization = `Bearer ${token}`; - } - return config; - }, - (error) => Promise.reject(error), -); - -export { instance as connect }; + (config) => { + const token = localStorage.getItem("authToken") || localStorage.getItem("token") + const c = config + if (token && config) { + c.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => Promise.reject(error), +) +export { instance as connect } diff --git a/src/pages/CourseExercises_ old.tsx b/src/pages/CourseExercises_ old.tsx new file mode 100644 index 0000000..dc03f4f --- /dev/null +++ b/src/pages/CourseExercises_ old.tsx @@ -0,0 +1,288 @@ +"use client" + +import { useState, useEffect } from "react" +import { useHistory, useParams } from "react-router-dom" + +import HeaderNav from "../components/HeaderNav" +import BottomNavigation from "../components/BottomNavigation" +import video from "../assets/img/video.mov" + +import { connect } from '../confconnect'; +import { getRouteExercise } from "../shared/consts/router" + +interface CourseExercise { + ID: number; + title: string; + desc: string; + url_file_img: string; +} + + + + +export const CourseExercises = () => { + const history = useHistory() + // const { id } = useParams<{ id: string }>(); + const [currentSlide, setCurrentSlide] = useState(0); + const token = localStorage.getItem('authToken'); + + useEffect(() => { + console.log(token) + if (!token) { + setError('Токен не найден'); + return; + } + + connect.get('/pacient/', { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then(response => { + console.log('Response status:', response.status); + + // Предполагаемая структура: + // response.data.courses — массив пользователей + const users = response.data.courses || []; + + // Собираем все курсы из всех пользователей + const allCourses: Course[] = []; + + users.forEach(user => { + if (user.Courses && Array.isArray(user.Courses)) { + user.Courses.forEach(course => { + // Можно добавить проверку или преобразование + allCourses.push({ + ID: course.id, // или course.ID, зависит от структуры + title: course.title, + desc: course.desc, + url_file_img: course.url_file_img, + }); + }); + } + }); + + setCourses(allCourses); + }) + .catch(error => { + if (error.response) { + console.error('Ошибка ответа сервера:', error.response.status, error.response.data); + setError(`Ошибка сервера: ${error.response.status}`); + } else if (error.request) { + console.error('Нет ответа от сервера:', error.request); + setError('Нет ответа от сервера'); + } else { + console.error('Ошибка при настройке запроса:', error.message); + setError(`Ошибка: ${error.message}`); + } + }); + }, [token]); + + +const course = { + id: 2, + description: 'Очень сильно', + name: 'Качаем ноги', + exercises: [ + { + id: 1, + name: "Подъемы ног лежа", + duration: "15 мин", + sets: 3, + reps: 12, + image: "/placeholder.svg?height=200&width=300", + difficulty: "Легкий", + description: "Укрепление мышц бедра и улучшение подвижности коленного сустава", + calories: 45, + }, + { + id: 2, + name: "Приседания у стены", + duration: "10 мин", + sets: 2, + reps: 15, + image: "/placeholder.svg?height=200&width=300", + difficulty: "Средний", + description: "Безопасные приседания для восстановления силы ног", + calories: 60, + }, + { + id: 3, + name: "Растяжка квадрицепса", + duration: "8 мин", + sets: 1, + reps: 30, + image: "/placeholder.svg?height=200&width=300", + difficulty: "Легкий", + description: "Улучшение гибкости и снятие напряжения", + calories: 25, + }, + { + id: 4, + name: "Укрепление икр", + duration: "12 мин", + sets: 3, + reps: 20, + image: "/placeholder.svg?height=200&width=300", + difficulty: "Средний", + description: "Развитие силы и выносливости икроножных мышц", + calories: 40, + }, + ]} + + // Функции для переключения на следующее/предыдущее упражнение + const nextExercise = () => { + setCurrentSlide((prev) => (prev + 1) % course.exercises.length) + } + + const prevExercise = () => { + setCurrentSlide((prev) => (prev - 1 + course.exercises.length) % course.exercises.length) + } + const currentExercise = course.exercises[currentSlide] + + // const getDifficultyColor = (difficulty: string) => { + // switch (difficulty) { + // case "Легкий": + // return "bg-gradient-to-r from-emerald-400 to-green-500" + // case "Средний": + // return "bg-gradient-to-r from-amber-400 to-orange-500" + // case "Сложный": + // return "bg-gradient-to-r from-red-400 to-pink-500" + // default: + // return "bg-gradient-to-r from-gray-400 to-gray-500" + // } + // } + + return ( + + + + +
+
+ + + + + {/* Exercise Slider */} +
+
+ {/* Exercise Image */} +
+
+ + {/* Exercise Info */} +
+

{currentExercise.name}

+ + + + +
+
+ + {/* Slide indicators */} +
+ {course.exercises.map((_, index) => ( +
+
+ + + + + + {/* Exercise List */} +
+

Все упражнения курса

+
+ {course.exercises.map((exercise, index) => ( +
history.push(getRouteExercise(currentExercise.id))} + className={`glass-morphism rounded-2xl p-4 sm:p-6 border border-white/50 shadow-lg cursor-pointer transition-all duration-300 hover:shadow-2xl transform hover:scale-[1.02] backdrop-blur-xl ${index === currentSlide ? "ring-2 ring-[#2BACBE] bg-cyan-50/20" : "" + }`} + > +
+
+
+ {index + 1} +
+ +
+
+

{exercise.name}

+

{exercise.description}

+ +
+
+ + + +
+
+
+ ))} +
+
+ + +
+
+ ) +} + + diff --git a/src/pages/Courses.tsx b/src/pages/Courses.tsx index 0c35e2d..451a377 100644 --- a/src/pages/Courses.tsx +++ b/src/pages/Courses.tsx @@ -98,6 +98,9 @@ export const Courses = () => { }); }, [token]); + + + // Генерируем случайный прогресс для каждого курса const getRandomProgress = () => Math.floor(Math.random() * 100); diff --git a/src/pages/Exercise old.tsx b/src/pages/Exercise old.tsx new file mode 100644 index 0000000..b43f781 --- /dev/null +++ b/src/pages/Exercise old.tsx @@ -0,0 +1,240 @@ +"use client" + +import { useState, useEffect } from "react" +import { useHistory } from "react-router-dom" + +import BottomNavigation from "../components/BottomNavigation" +import { getRouteCourseComplete } from "../shared/consts/router" +import HeaderNav from "../components/HeaderNav" +import video from "../assets/img/video.mov" + + +export interface Exercise { + id: number; + title: string; + desc: string; + url_file: string; + url_file_img: string; +} + + + + + +export const Exercise = () => { + const history = useHistory() + // const { id } = useParams<{ id: string }>() + const [isPlaying, setIsPlaying] = useState(false) + const [currentTime, setCurrentTime] = useState(0) + const [totalTime] = useState(900) // 15 minutes in seconds + const [currentSet, setCurrentSet] = useState(1) + const [totalSets] = useState(3) + + useEffect(() => { + let interval: NodeJS.Timeout | undefined + if (isPlaying) { + interval = setInterval(() => { + setCurrentTime((prev) => { + if (prev >= totalTime) { + setIsPlaying(false) + // Show completion animation + history.push(getRouteCourseComplete()) + return totalTime + } + return prev + 1 + }) + }, 1000) + } + return () => { + if (interval) clearInterval(interval) + } + }, [isPlaying, totalTime, history]) + + const formatTime = (seconds: number) => { + const mins = Math.floor(seconds / 60) + const secs = seconds % 60 + return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}` + } + + const progress = (currentTime / totalTime) * 100 + + + + const PlayIcon = () => ( + + + + ) + + const PauseIcon = () => ( + + + + ) + + const RefreshIcon = () => ( + + ) + + const CheckIcon = () => ( + + + + ) + + const exerciseSteps = [ + { + title: "Исходное положение", + description: + "Лягте на спирку на коврик. Руки вдоль тела, ладони прижаты к полу. Ноги выпрямлены, носки направлены вверх. Поясница плотно прижата к полу.", + }, + { + title: "Задание", + description: + "Медленно поднимите прямые ноги до угла 90 градусов. Задержитесь на 2 секунды, затем медленно опустите ноги, не касаясь пола. Повторите движение плавно и контролируемо.", + }, + { + title: "Подходы", + description: "Выполните 3 подхода по 12 повторений с отдыхом 60 секунд между подходами.", + }, + { + title: "Перерыв", + description: "Отдыхайте 60 секунд между подходами. Дышите спокойно и расслабьте мышцы.", + }, + { + title: "Динамика и статика", + description: + "Динамическая фаза: подъем и опускание ног выполняется плавно, 2 секунды вверх, 2 секунды вниз. Статическая фаза: удержание ног в верхней точке на 2 секунды.", + }, + ] + + + return ( + +
+
+ + + + +
+
+
+
+
+
+ +
+ {exerciseSteps.map((step, index) => ( +
+
+ +
+

{step.title}

+

{step.description}

+
+
+
+ ))} +
+ + {/* Fixed Timer at Bottom */} +
+
+
+
+
+ + Подход {currentSet} из {totalSets} + +
+ + {formatTime(currentTime)} / {formatTime(totalTime)} + +
+
+
+
+
+ + + +
+
+
+ +
+
+ ) +} + + diff --git a/src/pages/Exercise.tsx b/src/pages/Exercise.tsx index b43f781..5c1d9b0 100644 --- a/src/pages/Exercise.tsx +++ b/src/pages/Exercise.tsx @@ -1,34 +1,105 @@ "use client" import { useState, useEffect } from "react" -import { useHistory } from "react-router-dom" +import { useHistory, useParams } from "react-router-dom" import BottomNavigation from "../components/BottomNavigation" import { getRouteCourseComplete } from "../shared/consts/router" import HeaderNav from "../components/HeaderNav" import video from "../assets/img/video.mov" - +import { connect } from "../confconnect" export interface Exercise { - id: number; - title: string; - desc: string; - url_file: string; - url_file_img: string; + id: number + title: string + desc: string + url_file: string + url_file_img: string + time: string + repeats: number + count: number + position: number + day: number + sessionname: string } - - - +interface RouteParams { + courseId: string + exerciseId: string +} export const Exercise = () => { const history = useHistory() - // const { id } = useParams<{ id: string }>() + const { courseId, exerciseId } = useParams() + const [exercise, setExercise] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState("") const [isPlaying, setIsPlaying] = useState(false) const [currentTime, setCurrentTime] = useState(0) - const [totalTime] = useState(900) // 15 minutes in seconds + const [totalTime, setTotalTime] = useState(900) // Default 15 minutes const [currentSet, setCurrentSet] = useState(1) - const [totalSets] = useState(3) + const [totalSets, setTotalSets] = useState(3) + + useEffect(() => { + console.log("Course ID:", courseId) + console.log("Exercise ID:", exerciseId) + + if (!courseId || !exerciseId) { + setError("ID курса или упражнения не найден") + setLoading(false) + return + } + + // Получаем данные упражнения через API: GET /pacient/:course_id/:exercise_id + connect + .get(`pacient/${courseId}/${exerciseId}`) + .then((response) => { + console.log("Response status:", response.status) + console.log("Response data:", response.data) + + const exerciseData = response.data + + setExercise({ + id: exerciseData.id, + title: exerciseData.title, + desc: exerciseData.desc, + url_file: exerciseData.url_file, + url_file_img: exerciseData.url_file_img, + time: exerciseData.time, + repeats: exerciseData.repeats, + count: exerciseData.count, + position: exerciseData.position, + day: exerciseData.day, + sessionname: exerciseData.sessionname, + }) + + // Устанавливаем время и подходы из данных упражнения + if (exerciseData.time) { + setTotalTime(Number.parseInt(exerciseData.time) * 60) // Конвертируем минуты в секунды + } + if (exerciseData.count) { + setTotalSets(exerciseData.count) + } + + setLoading(false) + }) + .catch((error) => { + console.error("Ошибка при получении упражнения:", error) + + if (error.response) { + console.error("Ошибка ответа сервера:", error.response.status, error.response.data) + setError(`Ошибка сервера: ${error.response.status}`) + } else if (error.request) { + console.error("Нет ответа от сервера:", error.request) + setError("Нет ответа от сервера") + } else { + console.error("Ошибка при настройке запроса:", error.message) + setError(`Ошибка: ${error.message}`) + } + + setLoading(false) + }) + }, [courseId, exerciseId]) useEffect(() => { let interval: NodeJS.Timeout | undefined @@ -37,6 +108,8 @@ export const Exercise = () => { setCurrentTime((prev) => { if (prev >= totalTime) { setIsPlaying(false) + // Отправляем результат на сервер при завершении + submitProgress() // Show completion animation history.push(getRouteCourseComplete()) return totalTime @@ -50,6 +123,23 @@ export const Exercise = () => { } }, [isPlaying, totalTime, history]) + // Функция для отправки прогресса на сервер + const submitProgress = async () => { + if (!courseId || !exerciseId) return + + try { + const timeUsers = formatTime(currentTime) // Отправляем время в формате MM:SS + + await connect.post(`pacient/${courseId}/${exerciseId}`, { + time_users: timeUsers, + }) + + console.log("Прогресс отправлен на сервер:", timeUsers) + } catch (error) { + console.error("Ошибка при отправке прогресса:", error) + } + } + const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60) const secs = seconds % 60 @@ -58,8 +148,6 @@ export const Exercise = () => { const progress = (currentTime / totalTime) * 100 - - const PlayIcon = () => ( @@ -73,7 +161,20 @@ export const Exercise = () => { ) const RefreshIcon = () => ( - + + + + ) const CheckIcon = () => ( @@ -82,52 +183,94 @@ export const Exercise = () => { ) + // Генерируем инструкции на основе реальных данных упражнения const exerciseSteps = [ { - title: "Исходное положение", - description: - "Лягте на спирку на коврик. Руки вдоль тела, ладони прижаты к полу. Ноги выпрямлены, носки направлены вверх. Поясница плотно прижата к полу.", + title: "Описание упражнения", + description: exercise?.desc || "Выполните упражнение согласно инструкции.", }, { - title: "Задание", - description: - "Медленно поднимите прямые ноги до угла 90 градусов. Задержитесь на 2 секунды, затем медленно опустите ноги, не касаясь пола. Повторите движение плавно и контролируемо.", + title: "Продолжительность", + description: `Время выполнения: ${exercise?.time || 15} минут`, }, { - title: "Подходы", - description: "Выполните 3 подхода по 12 повторений с отдыхом 60 секунд между подходами.", + title: "Подходы и повторения", + description: `Выполните ${exercise?.count || 1} подход по ${exercise?.repeats || 12} повторений с отдыхом 60 секунд между подходами.`, }, { - title: "Перерыв", - description: "Отдыхайте 60 секунд между подходами. Дышите спокойно и расслабьте мышцы.", + title: "Позиция в программе", + description: `Это упражнение №${exercise?.position || 1} в программе дня ${exercise?.day || 1}.`, }, { - title: "Динамика и статика", - description: - "Динамическая фаза: подъем и опускание ног выполняется плавно, 2 секунды вверх, 2 секунды вниз. Статическая фаза: удержание ног в верхней точке на 2 секунды.", + title: "Техника безопасности", + description: "Следите за правильной техникой выполнения. При появлении боли немедленно прекратите упражнение.", }, ] + if (loading) { + return ( +
+
+
+
+

Загрузка упражнения...

+
+
+
+ ) + } + + if (error) { + return ( +
+
+ +
+
+

{error}

+ +
+
+
+
+ ) + } + + if (!exercise) { + return ( +
+
+ +
+
+

Упражнение не найдено

+ +
+
+
+
+ ) + } return ( -
- - - +
-