From 666b3a0508f9ea9f09476f91ce26fee3f79c988d Mon Sep 17 00:00:00 2001 From: Tatyana Date: Wed, 3 Sep 2025 14:16:56 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB=D0=B5=D0=BC?= =?UTF-8?q?=D0=B0=20=D1=81=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D0=BE=D0=BC=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B5=D1=81=D1=81?= =?UTF-8?q?=D0=B0=20=D0=BC=D0=B5=D0=B6=D0=B4=D1=83=20=D0=BA=D1=83=D1=80?= =?UTF-8?q?=D1=81=D0=B0=D0=BC=D0=B8=20/=D0=BA=D0=BB=D1=8E=D1=87=D0=B8=20lo?= =?UTF-8?q?calStorage=20=D1=81=20=20=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Exercise.tsx | 761 ++++++++++++++++++++++++++++++----------- src/pages/Home.tsx | 1 + 2 files changed, 564 insertions(+), 198 deletions(-) diff --git a/src/pages/Exercise.tsx b/src/pages/Exercise.tsx index 1e84631..b3480f1 100644 --- a/src/pages/Exercise.tsx +++ b/src/pages/Exercise.tsx @@ -1,95 +1,151 @@ "use client" +// Импортируем необходимые функции из React для создания интерактивного компонента import { useState, useEffect } from "react" +// Импортируем функции для навигации между страницами import { useHistory, useParams } from "react-router-dom" +// Импортируем иконки и компоненты интерфейса import { RefreshIcon } from "../components/icons/RefreshIcon" - import HeaderNav from "../components/HeaderNav" import BottomNavigation from "../components/BottomNavigation" +// Импортируем функцию для связи с сервером (бэкендом) import { connect } from "../confconnect" +// Описываем структуру данных упражнения - что должно содержать каждое упражнение export interface Exercise { - 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 + 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 // Название тренировочной сессии } +// Описываем параметры, которые приходят из URL страницы interface RouteParams { - courseId: string - exerciseId: string + courseId: string // ID курса (программы тренировок) + exerciseId: string // ID конкретного упражнения } export const Exercise = () => { + // Получаем функции для навигации и параметры из URL const history = useHistory() const { courseId, exerciseId } = useParams() + + // ========== ОСНОВНЫЕ СОСТОЯНИЯ КОМПОНЕНТА ========== + // Состояние для хранения данных упражнения (null = данные еще не загружены) const [exercise, setExercise] = useState(null) + + // Состояние загрузки (true = идет загрузка, false = загрузка завершена) const [loading, setLoading] = useState(true) + + // Состояние ошибки (пустая строка = нет ошибки, текст = описание ошибки) const [error, setError] = useState("") + + // ========== СОСТОЯНИЯ ТАЙМЕРА УПРАЖНЕНИЯ ========== + // Играет ли сейчас таймер упражнения (true = идет, false = на паузе) const [isPlaying, setIsPlaying] = useState(false) + + // Текущее время выполнения упражнения в секундах (0 = начало) const [currentTime, setCurrentTime] = useState(0) + + // Общее время упражнения в секундах (по умолчанию 15 минут = 900 секунд) const [totalTime, setTotalTime] = useState(900) + + // ========== СОСТОЯНИЯ ПОДХОДОВ (СЕТОВ) ========== + // Номер текущего подхода (начинаем с 1-го подхода) const [currentSet, setCurrentSet] = useState(1) + + // Общее количество подходов в упражнении (по умолчанию 3) const [totalSets, setTotalSets] = useState(3) - const [isRotating, setIsRotating] = useState(false) - const [hasSavedProgress, setHasSavedProgress] = useState(false) - const [isCompleted, setIsCompleted] = useState(false) + + // Массив номеров завершенных подходов (например: [1, 2] = завершены 1-й и 2-й подходы) const [completedSets, setCompletedSets] = useState([]) - // Новые состояния для отдыха - const [isResting, setIsResting] = useState(false) - const [restTime, setRestTime] = useState(0) - const [totalRestTime] = useState(60) // 60 секунд отдыха по умолчанию + // ========== СОСТОЯНИЯ ИНТЕРФЕЙСА ========== + // Крутится ли иконка обновления (для анимации) + const [isRotating, setIsRotating] = useState(false) - // Восстановление прогресса при загрузке + // Сохранен ли прогресс в памяти браузера (true = есть сохраненный прогресс) + const [hasSavedProgress, setHasSavedProgress] = useState(false) + + // Завершено ли упражнение полностью (true = все подходы выполнены) + const [isCompleted, setIsCompleted] = useState(false) + + // ========== СОСТОЯНИЯ ОТДЫХА МЕЖДУ ПОДХОДАМИ ========== + // Идет ли сейчас отдых между подходами (true = отдыхаем, false = выполняем упражнение) + const [isResting, setIsResting] = useState(false) + + // Оставшееся время отдыха в секундах (уменьшается каждую секунду) + const [restTime, setRestTime] = useState(0) + + // Общее время отдыха между подходами (60 секунд = 1 минута) + const [totalRestTime] = useState(60) + + // На паузе ли таймер отдыха (true = отдых приостановлен, false = отдых идет) + const [isRestPaused, setIsRestPaused] = useState(false) + + // ========== ВОССТАНОВЛЕНИЕ ПРОГРЕССА ПРИ ЗАГРУЗКЕ СТРАНИЦЫ ========== + // Этот код выполняется каждый раз при загрузке страницы или изменении упражнения useEffect(() => { - const savedProgress = localStorage.getItem("exerciseProgress") + // Пытаемся получить сохраненный прогресс из памяти браузера для конкретного курса и упражнения + const savedProgress = localStorage.getItem(`exerciseProgress_${courseId}_${exerciseId}`) + if (savedProgress) { + // Если прогресс найден, преобразуем его из текста в объект const progress = JSON.parse(savedProgress) - if (progress.exerciseId === exercise?.id) { - if (progress.status === 1) { - setIsCompleted(true) - setCurrentTime(progress.totalTime || totalTime) - setCompletedSets(progress.completedSets || []) - setCurrentSet(progress.set || totalSets) - setHasSavedProgress(false) - } else { - setCurrentTime(progress.position) - setCurrentSet(progress.set) - setCompletedSets(progress.completedSets || []) - setIsResting(progress.isResting || false) - setRestTime(progress.restTime || 0) - setHasSavedProgress(true) - setIsCompleted(false) - } + + // Проверяем статус сохраненного прогресса + if (progress.status === 1) { + // СТАТУС 1 = упражнение было полностью завершено ранее + console.log("Найден завершенный прогресс упражнения для курса", courseId) + setIsCompleted(true) // Помечаем как завершенное + setCurrentTime(progress.totalTime || totalTime) // Устанавливаем время на максимум + setCompletedSets(progress.completedSets || []) // Восстанавливаем завершенные подходы + setCurrentSet(progress.set || totalSets) // Устанавливаем текущий подход на последний + setHasSavedProgress(false) // Сбрасываем флаг сохранения + } else { + // СТАТУС 0 = упражнение было на паузе или в процессе выполнения + console.log("Найден незавершенный прогресс упражнения для курса", courseId) + setCurrentTime(progress.position) // Восстанавливаем время выполнения + setCurrentSet(progress.set) // Восстанавливаем номер подхода + setCompletedSets(progress.completedSets || []) // Восстанавливаем завершенные подходы + setIsResting(progress.isResting || false) // Восстанавливаем состояние отдыха + setRestTime(progress.restTime || 0) // Восстанавливаем время отдыха + setIsRestPaused(progress.isRestPaused || false) // Восстанавливаем паузу отдыха + setHasSavedProgress(true) // Помечаем что есть сохраненный прогресс + setIsCompleted(false) // Упражнение не завершено } } - }, [exercise, totalTime, totalSets]) + }, [exerciseId]) // Код выполняется при изменении ID упражнения + // ========== ЗАГРУЗКА ДАННЫХ УПРАЖНЕНИЯ С СЕРВЕРА ========== useEffect(() => { - console.log("Course ID:", courseId) - console.log("Exercise ID:", exerciseId) + console.log("Загружаем упражнение. ID курса:", courseId, "ID упражнения:", exerciseId) + // Проверяем, что у нас есть необходимые ID if (!courseId || !exerciseId) { setError("ID курса или упражнения не найден") setLoading(false) return } + // Отправляем запрос на сервер для получения данных упражнения connect .get(`pacient/${courseId}/${exerciseId}`) .then((response) => { + // Если запрос успешен, получаем данные упражнения const exerciseData = response.data + console.log("Данные упражнения получены:", exerciseData) + // Сохраняем данные упражнения в состояние компонента setExercise({ id: exerciseData.id, title: exerciseData.title, @@ -104,158 +160,338 @@ export const Exercise = () => { sessionname: exerciseData.sessionname, }) + // Если есть время упражнения, конвертируем минуты в секунды if (exerciseData.time) { const timeInSeconds = Number.parseInt(exerciseData.time) * 60 setTotalTime(timeInSeconds) console.log("Установлено время упражнения:", timeInSeconds, "секунд") } + + // Если есть количество подходов, устанавливаем его if (exerciseData.count) { setTotalSets(exerciseData.count) + console.log("Установлено количество подходов:", exerciseData.count) } + loadProgressFromServer(exerciseData.id) + + // Загрузка завершена успешно setLoading(false) }) .catch((error) => { + // Если произошла ошибка при загрузке console.error("Ошибка при получении упражнения:", error) + // Определяем тип ошибки и показываем соответствующее сообщение if (error.response) { + // Сервер ответил с ошибкой (например, 404, 500) 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]) - - // Функция для завершения текущего подхода - const handleCompleteSet = async () => { - console.log("Завершение подхода", currentSet, "из", totalSets) - - const newCompletedSets = [...completedSets, currentSet] - setCompletedSets(newCompletedSets) - setIsPlaying(false) - - // Проверяем, завершены ли все подходы - if (newCompletedSets.length >= totalSets) { - console.log("Все подходы завершены, завершаем упражнение") - // Все подходы завершены - завершаем упражнение - await handleComplete(newCompletedSets) - } else { - console.log("Начинаем отдых перед подходом", currentSet + 1) - // Начинаем отдых перед следующим подходом - setIsResting(true) - setRestTime(totalRestTime) - setCurrentTime(0) - - // Сохраняем прогресс с отдыхом - await handlePause(newCompletedSets, currentSet, true, totalRestTime) - } - } - - // Функция для завершения упражнения - const handleComplete = async (completedSetsArray = completedSets) => { - console.log("Завершение упражнения") - - const completionData = { - time_users: formatTime(totalTime), - status: 1, - } - - localStorage.setItem( - "exerciseProgress", - JSON.stringify({ - exerciseId: exercise?.id, - position: totalTime, - set: totalSets, - status: 1, - totalTime: totalTime, - completedSets: completedSetsArray, - completedAt: new Date().toISOString(), - }), - ) - - setIsCompleted(true) - setCurrentTime(totalTime) - setIsPlaying(false) - setIsResting(false) - setHasSavedProgress(false) - setCompletedSets(completedSetsArray) + }, [courseId, exerciseId]) // Код выполняется при изменении ID курса или упражнения + // ========== ФУНКЦИЯ ЗАГРУЗКИ ПРОГРЕССА С СЕРВЕРА ========== + // Эта функция получает сохраненный прогресс упражнения с бэкенда + const loadProgressFromServer = async (exerciseId: number) => { try { - console.log("Отправляем данные о завершении:", completionData) + console.log("Загружаем сохраненный прогресс с сервера для упражнения:", exerciseId, "в курсе:", courseId) - const response = await connect.post(`pacient/${courseId}/${exerciseId}`, completionData) + const response = await connect.get(`pacient/${courseId}/${exerciseId}/progress?course_id=${courseId}`) - console.log("Ответ сервера при завершении:", response.status) - console.log("Упражнение успешно завершено") + if (response.data && response.data.length > 0) { + console.log("Найден сохраненный прогресс на сервере для курса", courseId, ":", response.data) + + // Получаем все записи прогресса для этого упражнения В ЭТОМ КУРСЕ + const progressData = response.data + + const filteredProgress = progressData.filter((record: any) => { + // Проверяем что запись относится к текущему курсу + return record.course_id === Number.parseInt(courseId) || record.CourseId === Number.parseInt(courseId) + }) + + console.log("Отфильтрованный прогресс для курса", courseId, ":", filteredProgress) + + // Находим завершенные подходы (status = 1) и незавершенные (status = 0) + const completedSetsFromServer: number[] = [] + let lastIncompleteSet = null + let lastSavedTime = "00:00" + + // Обрабатываем каждую запись прогресса ТОЛЬКО ДЛЯ ТЕКУЩЕГО КУРСА + filteredProgress.forEach((record: any) => { + console.log( + `Обрабатываем запись для курса ${courseId}: подход ${record.set}, статус ${record.status}, время ${record.time_users}`, + ) + + if (record.status === 1) { + // СТАТУС 1 = подход завершен + completedSetsFromServer.push(record.set) + console.log(`Подход ${record.set} завершен`) + } else if (record.status === 0) { + // СТАТУС 0 = подход в процессе или на паузе + lastIncompleteSet = record + lastSavedTime = record.time_users || "00:00" + console.log(`Подход ${record.set} в процессе, время: ${lastSavedTime}`) + } + }) + + // Восстанавливаем состояние на основе данных с сервера + if (completedSetsFromServer.length > 0) { + console.log("Восстанавливаем завершенные подходы:", completedSetsFromServer) + setCompletedSets(completedSetsFromServer) + } + + // Если есть незавершенный подход, восстанавливаем его состояние + if (lastIncompleteSet) { + console.log("Восстанавливаем незавершенный подход:", lastIncompleteSet.set) + + // Конвертируем время из формата MM:SS в секунды + const timeInSeconds = convertTimeToSeconds(lastSavedTime) + + setCurrentSet(lastIncompleteSet.set) // Устанавливаем текущий подход + setCurrentTime(timeInSeconds) // Восстанавливаем время + setHasSavedProgress(true) // Помечаем что есть сохраненный прогресс + setIsCompleted(false) // Упражнение не завершено + + console.log(`Восстановлено состояние: подход ${lastIncompleteSet.set}, время ${timeInSeconds} секунд`) + } + + // Проверяем, завершены ли все подходы + if (completedSetsFromServer.length >= totalSets) { + console.log("ВСЕ ПОДХОДЫ ЗАВЕРШЕНЫ согласно серверу!") + setIsCompleted(true) // Помечаем упражнение как завершенное + setCurrentTime(totalTime) // Устанавливаем время на максимум + setHasSavedProgress(false) // Сбрасываем флаг сохранения + } + } else { + console.log("Прогресс на сервере не найден, начинаем с чистого листа") + // Если прогресса нет, оставляем начальные значения + } } catch (error) { - console.error("Ошибка при отправке завершения:", error) - if (error.response) { - console.error("Ответ сервера:", error.response.status, error.response.data) + console.error("Ошибка при загрузке прогресса с сервера:", error) + + // Если произошла ошибка, пытаемся загрузить из localStorage как резервный вариант + console.log("Пытаемся загрузить прогресс из localStorage как резервный вариант") + + // Пытаемся получить сохраненный прогресс из памяти браузера для конкретного курса и упражнения + const savedProgress = localStorage.getItem(`exerciseProgress_${courseId}_${exerciseId}`) + + if (savedProgress) { + const progress = JSON.parse(savedProgress) + + console.log("Найден резервный прогресс в localStorage для курса", courseId, ":", progress) + + if (progress.status === 1) { + // СТАТУС 1 = упражнение было полностью завершено ранее + setIsCompleted(true) + setCurrentTime(progress.totalTime || totalTime) + setCompletedSets(progress.completedSets || []) + setCurrentSet(progress.set || totalSets) + setHasSavedProgress(false) + } else { + // СТАТУС 0 = упражнение было на паузе или в процессе выполнения + setCurrentTime(progress.position) + setCurrentSet(progress.set) + setCompletedSets(progress.completedSets || []) + setIsResting(progress.isResting || false) + setRestTime(progress.restTime || 0) + setIsRestPaused(progress.isRestPaused || false) + setHasSavedProgress(true) + setIsCompleted(false) + } } } } + // ========== ФУНКЦИЯ КОНВЕРТАЦИИ ВРЕМЕНИ ИЗ MM:SS В СЕКУНДЫ ========== + // Преобразует время из формата "MM:SS" в секунды (например: "02:30" = 150 секунд) + const convertTimeToSeconds = (timeString: string): number => { + try { + // Разделяем строку по двоеточию + const parts = timeString.split(":") + + if (parts.length === 2) { + const minutes = Number.parseInt(parts[0]) || 0 // Получаем минуты + const seconds = Number.parseInt(parts[1]) || 0 // Получаем секунды + const totalSeconds = minutes * 60 + seconds // Конвертируем в секунды + + console.log(`Конвертируем время ${timeString} в ${totalSeconds} секунд`) + return totalSeconds + } else { + console.warn("Неверный формат времени:", timeString) + return 0 + } + } catch (error) { + console.error("Ошибка при конвертации времени:", error) + return 0 + } + } + + // ========== ФУНКЦИЯ СОХРАНЕНИЯ ПРОГРЕССА ПОДХОДА НА СЕРВЕР ========== + const saveSetProgress = async (setNumber: number, timeString: string, status: number) => { + try { + console.log(`Сохраняем прогресс подхода ${setNumber} со статусом ${status}`) + + const progressData = { + time_users: timeString, // Время в формате MM:SS + set: setNumber, // Номер подхода (1, 2, 3...) + status: status, // 0 = в процессе, 1 = завершен + course_id: Number.parseInt(courseId), // ВАЖНО: добавляем ID курса для правильной фильтрации + } + + console.log("Отправляем данные на сервер:", progressData) + + // Отправляем POST запрос на сервер для сохранения прогресса + const response = await connect.post(`pacient/${courseId}/${exerciseId}`, progressData) + + if (response.status === 200) { + console.log("Прогресс успешно сохранен на сервере") + } + } catch (error) { + console.error("Ошибка при сохранении прогресса на сервер:", error) + } + } + + // ========== ФУНКЦИЯ ПЕРЕХОДА К СЛЕДУЮЩЕМУ УПРАЖНЕНИЮ ========== + const goToNextExercise = () => { + console.log("Переходим к следующему упражнению") + + // Получаем текущий номер упражнения и увеличиваем на 1 + const currentExerciseNum = Number.parseInt(exerciseId) + const nextExerciseId = currentExerciseNum + 1 + + console.log(`Текущее упражнение: ${currentExerciseNum}, следующее: ${nextExerciseId}`) + + // Переходим к следующему упражнению в том же курсе + history.push(`/pacient/course/${courseId}/${nextExerciseId}`) + } + + // ========== ФУНКЦИЯ ЗАВЕРШЕНИЯ ТЕКУЩЕГО ПОДХОДА ========== + const handleCompleteSet = async () => { + console.log("Пользователь завершает подход", currentSet, "из", totalSets) + + // Сохраняем завершенный подход на сервер со статусом 1 (завершен) + await saveSetProgress(currentSet, formatTime(currentTime), 1) + + // Добавляем текущий подход в список завершенных + const newCompletedSets = [...completedSets, currentSet] + setCompletedSets(newCompletedSets) + + // Останавливаем таймер упражнения + setIsPlaying(false) + + // Проверяем, завершены ли все подходы + if (newCompletedSets.length >= totalSets) { + console.log("ВСЕ ПОДХОДЫ ЗАВЕРШЕНЫ! Упражнение выполнено полностью.") + // Все подходы завершены - завершаем упражнение полностью + await handleComplete(newCompletedSets) + } else { + console.log("Начинаем отдых перед подходом", currentSet + 1) + // Еще есть подходы - начинаем отдых перед следующим подходом + setIsResting(true) // Включаем режим отдыха + setIsRestPaused(false) // Отдых не на паузе + setRestTime(totalRestTime) // Устанавливаем полное время отдыха + setCurrentTime(0) // Сбрасываем время упражнения + + // Сохраняем прогресс с информацией об отдыхе + await handlePause(newCompletedSets, currentSet, true, totalRestTime, false) + } + } + + // ========== ФУНКЦИЯ ПОЛНОГО ЗАВЕРШЕНИЯ УПРАЖНЕНИЯ ========== + const handleComplete = async (completedSetsArray = completedSets) => { + console.log("УПРАЖНЕНИЕ ПОЛНОСТЬЮ ЗАВЕРШЕНО! Все подходы выполнены.") + + // Сохраняем в память браузера как завершенное (СТАТУС 1 = ЗАВЕРШЕНО) + localStorage.setItem( + `exerciseProgress_${courseId}_${exerciseId}`, + JSON.stringify({ + exerciseId: exercise?.id, // ID упражнения + courseId: courseId, // ID курса для дополнительной проверки + position: totalTime, // Позиция на максимальном времени + set: totalSets, // Все подходы завершены + status: 1, // СТАТУС 1 = ВСЕ УПРАЖНЕНИЕ ЗАВЕРШЕНО + totalTime: totalTime, // Общее время упражнения + completedSets: completedSetsArray, // Список завершенных подходов + completedAt: new Date().toISOString(), // Время завершения + }), + ) + + // Обновляем состояние компонента + setIsCompleted(true) // Помечаем как завершенное + setCurrentTime(totalTime) // Устанавливаем время на максимум + setIsPlaying(false) // Останавливаем таймер + setIsResting(false) // Выключаем режим отдыха + setIsRestPaused(false) // Убираем паузу отдыха + setHasSavedProgress(false) // Сбрасываем флаг сохранения + setCompletedSets(completedSetsArray) // Обновляем завершенные подходы + } + + // ========== ФУНКЦИЯ ПАУЗЫ И СОХРАНЕНИЯ ПРОГРЕССА ========== const handlePause = async ( completedSetsArray = completedSets, setNumber = currentSet, resting = isResting, currentRestTime = restTime, + restPaused = isRestPaused, ) => { - const progressData = { - time_users: formatTime(currentTime), - status: setNumber, - } + console.log("Сохраняем прогресс на паузе для курса", courseId, "упражнения", exerciseId) + // Сохраняем как незавершенное (СТАТУС 0 = В ПРОЦЕССЕ/НА ПАУЗЕ) + await saveSetProgress(setNumber, formatTime(currentTime), 0) + + // Сохраняем прогресс в память браузера localStorage.setItem( - "exerciseProgress", + `exerciseProgress_${courseId}_${exerciseId}`, JSON.stringify({ - exerciseId: exercise?.id, - position: currentTime, - set: setNumber, - status: 0, - completedSets: completedSetsArray, - isResting: resting, - restTime: currentRestTime, + exerciseId: exercise?.id, // ID упражнения + courseId: courseId, // ID курса для дополнительной проверки + position: currentTime, // Текущая позиция времени + set: setNumber, // Текущий подход + status: 0, // СТАТУС 0 = УПРАЖНЕНИЕ НА ПАУЗЕ ИЛИ В ПРОЦЕССЕ + completedSets: completedSetsArray, // Завершенные подходы + isResting: resting, // Идет ли отдых + restTime: currentRestTime, // Оставшееся время отдыха + isRestPaused: restPaused, // На паузе ли отдых }), ) + // Помечаем что прогресс сохранен setHasSavedProgress(true) - - try { - console.log("Отправляем данные:", progressData) - - const response = await connect.post(`pacient/${courseId}/${exerciseId}`, progressData) - - console.log("Ответ сервера при отправке прогресса:", response.status) - console.log("Прогресс успешно сохранен на сервере") - } catch (error) { - console.error("Ошибка при отправке прогресса:", error) - if (error.response) { - console.error("Ответ сервера:", error.response.status, error.response.data) - } - } } - // Основной таймер для упражнения + // ========== ОСНОВНОЙ ТАЙМЕР ДЛЯ УПРАЖНЕНИЯ ========== + // Этот код выполняется каждую секунду когда упражнение активно useEffect(() => { let interval: NodeJS.Timeout | undefined + + // Таймер работает только если: + // - упражнение запущено (isPlaying = true) + // - упражнение не завершено (isCompleted = false) + // - не идет отдых (isResting = false) if (isPlaying && !isCompleted && !isResting) { + console.log("Запускаем таймер упражнения") + interval = setInterval(() => { setCurrentTime((prev) => { const newTime = prev + 1 - console.log(`Таймер: ${newTime}/${totalTime} секунд`) + console.log(`Таймер упражнения: ${newTime}/${totalTime} секунд`) // Проверяем, достигли ли мы конца времени для текущего подхода if (newTime >= totalTime) { - console.log("Время подхода истекло, завершаем подход") + console.log("Время подхода истекло, автоматически завершаем подход") setIsPlaying(false) + // Используем setTimeout чтобы избежать проблем с состоянием в useEffect setTimeout(() => { handleCompleteSet() @@ -264,79 +500,120 @@ export const Exercise = () => { } return newTime }) - }, 1000) + }, 1000) // Выполняется каждую секунду (1000 миллисекунд) } + + // Очищаем таймер при размонтировании компонента или изменении зависимостей return () => { - if (interval) clearInterval(interval) + if (interval) { + console.log("Останавливаем таймер упражнения") + clearInterval(interval) + } } }, [isPlaying, totalTime, isCompleted, isResting, currentSet, completedSets]) - // Таймер для отдыха + // ========== ТАЙМЕР ДЛЯ ОТДЫХА МЕЖДУ ПОДХОДАМИ ========== + // Этот код выполняется каждую секунду во время отдыха useEffect(() => { let restInterval: NodeJS.Timeout | undefined - if (isResting && restTime > 0) { + + // Таймер отдыха работает только если: + // - идет отдых (isResting = true) + // - есть оставшееся время отдыха (restTime > 0) + // - отдых не на паузе (isRestPaused = false) + if (isResting && restTime > 0 && !isRestPaused) { + console.log("Запускаем таймер отдыха") + restInterval = setInterval(() => { setRestTime((prev) => { const newRestTime = prev - 1 - console.log(`Отдых: ${newRestTime} секунд осталось`) + console.log(`Таймер отдыха: ${newRestTime} секунд осталось`) + // Если время отдыха закончилось if (newRestTime <= 0) { console.log("Отдых закончен, переходим к следующему подходу") - // Отдых закончен, переходим к следующему подходу - setIsResting(false) - setCurrentSet(currentSet + 1) - setCurrentTime(0) + + // Выключаем режим отдыха и переходим к следующему подходу + setIsResting(false) // Выключаем отдых + setIsRestPaused(false) // Убираем паузу + setCurrentSet(currentSet + 1) // Переходим к следующему подходу + setCurrentTime(0) // Сбрасываем время упражнения return 0 } return newRestTime }) - }, 1000) + }, 1000) // Выполняется каждую секунду } - return () => { - if (restInterval) clearInterval(restInterval) - } - }, [isResting, restTime, currentSet]) + // Очищаем таймер при размонтировании компонента или изменении зависимостей + return () => { + if (restInterval) { + console.log("Останавливаем таймер отдыха") + clearInterval(restInterval) + } + } + }, [isResting, restTime, isRestPaused, currentSet]) + + // ========== ФУНКЦИИ ДЛЯ АНИМАЦИИ КНОПКИ СБРОСА ========== const handleClick = () => { + console.log("Запускаем анимацию кнопки сброса") setIsRotating(true) } const handleTransitionEnd = () => { + console.log("Анимация кнопки сброса завершена") setIsRotating(false) } + // ========== ФУНКЦИЯ ФОРМАТИРОВАНИЯ ВРЕМЕНИ ========== + // Преобразует секунды в формат MM:SS (например: 125 секунд = "02:05") const formatTime = (seconds: number) => { - const mins = Math.floor(seconds / 60) - const secs = seconds % 60 + const mins = Math.floor(seconds / 60) // Получаем минуты + const secs = seconds % 60 // Получаем оставшиеся секунды + // Добавляем ведущий ноль если число меньше 10 (например: 5 -> "05") return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}` } - const progress = isResting ? ((totalRestTime - restTime) / totalRestTime) * 100 : (currentTime / totalTime) * 100 + // ========== РАСЧЕТ ПРОГРЕССА ДЛЯ ПОЛОСКИ ========== + // Вычисляем процент заполнения полоски прогресса (от 0 до 100) + const progress = isResting + ? ((totalRestTime - restTime) / totalRestTime) * 100 // Прогресс отдыха + : (currentTime / totalTime) * 100 // Прогресс упражнения + // ========== ИКОНКИ ДЛЯ КНОПОК ========== + // SVG иконка "Играть" (треугольник) const PlayIcon = () => ( ) + // SVG иконка "Пауза" (две вертикальные полоски) const PauseIcon = () => ( ) + // SVG иконка "Галочка" (завершено) const CheckIcon = () => ( ) + // SVG иконка "Часы" (для отдыха) const RestIcon = () => ( - - + + ) + // ========== ИНФОРМАЦИЯ ОБ УПРАЖНЕНИИ ДЛЯ ОТОБРАЖЕНИЯ ========== + // Массив с описанием шагов упражнения const exerciseSteps = [ { title: "Описание упражнения", @@ -360,6 +637,9 @@ export const Exercise = () => { }, ] + // ========== ЭКРАНЫ ЗАГРУЗКИ И ОШИБОК ========== + + // Если данные еще загружаются, показываем экран загрузки if (loading) { return (
@@ -373,10 +653,11 @@ export const Exercise = () => { ) } + // Если произошла ошибка, показываем экран ошибки if (error) { return (
-
+
@@ -394,10 +675,11 @@ export const Exercise = () => { ) } + // Если упражнение не найдено, показываем соответствующее сообщение if (!exercise) { return (
-
+
@@ -415,41 +697,48 @@ export const Exercise = () => { ) } + // ========== ОСНОВНОЙ ИНТЕРФЕЙС УПРАЖНЕНИЯ ========== return (
+ {/* Заголовок страницы с названием упражнения */} + {/* Временное предупреждение для разработчика */} +

Если упражнение меньше минуты, то скидывает сразу на отдых

-

Если упражнение меньше минуты, то скидывает сразу на отдых

+ {/* Основная карточка с изображением и кнопкой воспроизведения */}
+ {/* Изображение упражнения */} {exercise.title} { + // Если изображение не загрузилось, показываем заглушку const target = e.target as HTMLImageElement target.src = "/placeholder.svg?height=300&width=400&text=Упражнение" }} /> + {/* Градиент поверх изображения для лучшей читаемости */}
+ {/* Центральная кнопка управления */}
{isCompleted ? ( -
+ // Если упражнение завершено - показываем галочку +
) : isResting ? ( + // Если идет отдых - показываем иконку часов
- - - - - +
) : ( + // Если упражнение активно - показываем кнопку воспроизведения/паузы
- {/* Статус выполнения */} + {/* Индикаторы статуса в левом верхнем углу */} {isCompleted && (
-
- +
+ Выполнено
@@ -476,9 +765,9 @@ export const Exercise = () => { {isResting && (
-
+
- Отдых + {isRestPaused ? "Отдых на паузе" : "Отдых"}
)} @@ -492,6 +781,7 @@ export const Exercise = () => {
)} + {/* Таймер в правом верхнем углу */}
{isResting ? formatTime(restTime) : formatTime(currentTime)} @@ -501,28 +791,32 @@ export const Exercise = () => {
- {/* Индикатор завершенных подходов */} + {/* Индикатор прогресса подходов */}

Прогресс подходов

+ {/* Создаем полоски для каждого подхода */} {Array.from({ length: totalSets }, (_, index) => { - const setNumber = index + 1 - const isSetCompleted = completedSets.includes(setNumber) - const isCurrent = setNumber === currentSet && !isSetCompleted + const setNumber = index + 1 // Номер подхода (начинаем с 1) + const isSetCompleted = completedSets.includes(setNumber) // Завершен ли этот подход + const isCurrent = setNumber === currentSet && !isSetCompleted // Текущий ли это подход return (
+ {/* Полоска прогресса для подхода */}
+ {/* Номер подхода под полоской */}
{setNumber}
) })}
+ {/* Общая статистика */}
Завершено: {completedSets.length} Всего: {totalSets} @@ -530,6 +824,7 @@ export const Exercise = () => {
+ {/* Информационные карточки об упражнении */}
{exerciseSteps.map((step, index) => (
{ ))}
- {/* Fixed Timer at Bottom */} + {/* Фиксированная панель управления внизу экрана */}
+ {/* Информация о текущем состоянии */}
+ {/* Индикатор активности */}
+ {/* Текст состояния */} {isCompleted - ? `Завершено` + ? `Все подходы завершены!` : isResting - ? `Отдых перед подходом ${currentSet + 1}` + ? `Отдых перед подходом ${currentSet + 1}${isRestPaused ? " (пауза)" : ""}` : `Подход ${currentSet} из ${totalSets}`}
+ {/* Отображение времени */} {isResting ? `${formatTime(restTime)} отдых` : `${formatTime(currentTime)} / ${formatTime(totalTime)}`}
+ + {/* Полоска прогресса */}
{ style={{ width: `${progress}%` }} >
+ + {/* Кнопки управления */}
{isCompleted ? ( -
- - Упражнение выполнено -
+ // Если упражнение завершено - показываем кнопки перехода и статуса + <> + +
+ + Завершено +
+ ) : isResting ? ( -
- - Отдых {formatTime(restTime)} -
+ // Если идет отдых - показываем кнопки управления отдыхом + <> + + + ) : ( + // Если упражнение активно - показываем основные кнопки управления <> + {/* Кнопка завершения текущего подхода */} )} + {/* Кнопка сброса (всегда доступна) */}
+ + {/* Нижняя навигация */}
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 1abf7b6..a2dfa80 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -35,6 +35,7 @@ export default function Home() { const exerciseProgress = localStorage.getItem('exerciseProgress'); const currentProgress = localStorage.getItem('currentProgress') + useEffect(() => {