From 2b9c198e44ddd0087be790115674099c17049251 Mon Sep 17 00:00:00 2001 From: Tatyana Date: Thu, 4 Sep 2025 12:08:54 +0300 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=BB=D0=B0=D1=81=D1=8C=20=D1=81=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B5=D0=B9=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B5=D1=81=D1=81?= =?UTF-8?q?=D0=B0=20=D1=81=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Exercise.tsx | 226 ++++---- src/pages/ExerciseOld.tsx | 1025 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1149 insertions(+), 102 deletions(-) create mode 100644 src/pages/ExerciseOld.tsx diff --git a/src/pages/Exercise.tsx b/src/pages/Exercise.tsx index ac02749..f0131dc 100644 --- a/src/pages/Exercise.tsx +++ b/src/pages/Exercise.tsx @@ -41,12 +41,28 @@ export const Exercise = () => { // ========== ОСНОВНЫЕ СОСТОЯНИЯ КОМПОНЕНТА ========== // Состояние для хранения данных упражнения (null = данные еще не загружены) + // Мы говорим TypeScript, что переменная exercise может быть либо объектом типа Exercise, либо null. Это важно, потому что изначально у нас нет данных (например, мы их ещё не загрузили), и состояние пустое — null + + + // exercise — это переменная/ текущее значение состояния, которое может быть объектом Exercise или null. + // setExercise — функция, с помощью которой можно обновить exercise. + + + // useState("") — это вызов функции useState с аргументом "" (пустая строка). + // Внутри угловых скобок мы указываем тип состояния (TypeScript). + // В круглых скобках ("") — передаём начальное значение состояния. + const [exercise, setExercise] = useState(null) + // useState(null) — это вызов функции useState с начальным значением null // Состояние загрузки (true = идет загрузка, false = загрузка завершена) const [loading, setLoading] = useState(true) + // если TypeScript может сам вывести тип из переданного начального значения, то указывать тип явно не обязательно // Состояние ошибки (пустая строка = нет ошибки, текст = описание ошибки) + // С помощью ДЕСТРУКТУРИЗАЦИИ МАССИВА мы присваиваем первый элемент массива переменной error, а второй — функции setError + // Деструктуризация массива — это удобный синтаксис в JavaScript/TypeScript, который позволяет распаковать значения из массива в отдельные переменные + // Когда начальное значение — null или undefined, и TypeScript не может понять, какого типа будет состояние. const [error, setError] = useState("") // ========== СОСТОЯНИЯ ТАЙМЕРУ УПРАЖНЕНИЯ ========== @@ -98,6 +114,10 @@ export const Exercise = () => { const loadProgress = async () => { const serverLoaded = await loadProgressFromServer() + // async — говорит, что функция работает асинхронно и возвращает промис. + // await — заставляет функцию ждать завершения промиса, не блокируя при этом весь поток выполнения. + + if (!serverLoaded) { // Fallback to localStorage if server loading failed console.log("Пытаемся загрузить прогресс из localStorage как резервный вариант") @@ -208,90 +228,98 @@ export const Exercise = () => { setLoading(false) }) }, [courseId, exerciseId]) // Код выполняется при изменении ID курса или упражнения + + + // ========== ФУНКЦИЯ ЗАГРУЗКИ ПРОГРЕССА С СЕРВЕРА ========== // Эта функция получает сохраненный прогресс упражнения с бэкенда - const loadProgressFromServer = async (): Promise => { - try { - console.log("Загружаем сохраненный прогресс с сервера для упражнения:", exerciseId, "в курсе:", courseId) +const loadProgressFromServer = async () => { + try { + console.log(`Загружаем прогресс для курса ${courseId} и упражнения ${exerciseId}`); - const response = await connect.get(`pacient/${courseId}/${exerciseId}/progress?course_id=${courseId}`) + const token = localStorage.getItem('authToken'); - if (response.data && response.data.length > 0) { - console.log("Найден сохраненный прогресс на сервере для курса", courseId, ":", response.data) + // Выполняем GET-запрос на сервер + const response = await connect.get(`pacient/${courseId}/${exerciseId}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); - // Получаем все записи прогресса для этого упражнения В ЭТОМ КУРСЕ - const progressData = response.data + // Логируем ответ + console.log("Ответ сервера с прогрессом:", response.data); - const filteredProgress = progressData.filter((record: any) => { - // Проверяем что запись относится к текущему курсу - return record.course_id === Number.parseInt(courseId) || record.CourseId === Number.parseInt(courseId) - }) + // Проверяем наличие user_progress + if ( + response.data && + response.data.user_progress && + Array.isArray(response.data.user_progress) && + response.data.user_progress.length > 0 + ) { + const progressArray = response.data.user_progress; - console.log("Отфильтрованный прогресс для курса", courseId, ":", filteredProgress) + // Фильтруем прогресс по текущему курсу + const filteredProgress = progressArray.filter((record: any) => { + return record.course_id === Number.parseInt(courseId); + }); - // Находим завершенные подходы (status = 1) и незавершенные (status = 0) - const completedSetsFromServer: number[] = [] - let lastIncompleteSet = null - let lastSavedTime = "00:00" + console.log("Отфильтрованный прогресс:", filteredProgress); - // Обрабатываем каждую запись прогресса ТОЛЬКО ДЛЯ ТЕКУЩЕГО КУРСА - filteredProgress.forEach((record: any) => { - console.log( - `Обрабатываем запись для курса ${courseId}: подход ${record.set}, статус ${record.status}, время ${record.time_users}`, - ) + const completedSetsFromServer: number[] = []; + let lastIncompleteSet: any = null; + let lastSavedTime = "00:00"; - 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}`) + // Обрабатываем каждый прогресс + filteredProgress.forEach((record: any) => { + console.log(`Подход ${record.set}, статус ${record.status}, время ${record.time_users}`); + + if (record.status === 1) { + // Завершённый подход + completedSetsFromServer.push(record.set); + } else if (record.status === 0) { + // Незавершённый подход + if (!lastIncompleteSet || record.set > lastIncompleteSet.set) { + lastIncompleteSet = record; + lastSavedTime = record.time_users || "00:00"; } - }) - - // Восстанавливаем состояние на основе данных с сервера - 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) // Сбрасываем флаг сохранения - } - - return true // Successfully loaded from server - } else { - console.log("Прогресс на сервере не найден, начинаем с чистого листа") - return false // No progress found on server + // Восстановление завершённых подходов + if (completedSetsFromServer.length > 0) { + setCompletedSets(completedSetsFromServer); } - } catch (error) { - console.error("Ошибка при загрузке прогресса с сервера:", error) - return false // Failed to load from server - } - } + // Восстановление незавершённого подхода + if (lastIncompleteSet) { + const timeInSeconds = convertTimeToSeconds(lastSavedTime); + + setCurrentSet(lastIncompleteSet.set); + setCurrentTime(timeInSeconds); + setHasSavedProgress(true); + setIsCompleted(false); + + console.log(`Восстановлен незавершённый подход ${lastIncompleteSet.set} с временем ${timeInSeconds} секунд`); + } + + // Проверка, завершены ли все подходы + if (completedSetsFromServer.length >= totalSets) { + setIsCompleted(true); + setCurrentTime(totalTime); + setHasSavedProgress(false); + console.log("Все подходы завершены"); + } + + return true; + } else { + console.log("Прогресс не найден, начинаем с чистого листа"); + return false; + } + } catch (error) { + console.error("Ошибка при загрузке прогресса с сервера:", error); + return false; + } +}; // ========== ФУНКЦИЯ КОНВЕРТАЦИИ ВРЕМЕНИ ИЗ MM:SS В СЕКУНДЫ ========== // Преобразует время из формата "MM:SS" в секунды (например: "02:30" = 150 секунд) const convertTimeToSeconds = (timeString: string): number => { @@ -349,8 +377,8 @@ export const Exercise = () => { const nextExerciseId = currentExerciseNum; console.log(`!!!!Текущее упражнение: ${currentExerciseNum}, следующее: ${nextExerciseId}`) - - console.log({courseId}, {nextExerciseId}) + + console.log({ courseId }, { nextExerciseId }) // Переходим к следующему упражнению в том же курсе history.push(`${courseId}/${nextExerciseId}`) } @@ -718,11 +746,10 @@ export const Exercise = () => { @@ -782,9 +809,8 @@ export const Exercise = () => {
{/* Полоска прогресса для подхода */}
{/* Номер подхода под полоской */}
{setNumber}
@@ -825,13 +851,12 @@ export const Exercise = () => {
{/* Индикатор активности */}
{/* Текст состояния */} @@ -851,13 +876,12 @@ export const Exercise = () => { {/* Полоска прогресса */}
@@ -889,11 +913,10 @@ export const Exercise = () => { // Сохраняем состояние паузы handlePause(completedSets, currentSet, true, restTime, !isRestPaused) }} - className={`flex-1 font-bold py-3 px-4 rounded-xl transition-all duration-300 flex items-center justify-center space-x-2 ${ - isRestPaused - ? "bg-yellow-500 hover:bg-yellow-600 text-white" - : "bg-[#2BACBE] hover:bg-[#2099A8] text-white" - }`} + className={`flex-1 font-bold py-3 px-4 rounded-xl transition-all duration-300 flex items-center justify-center space-x-2 ${isRestPaused + ? "bg-yellow-500 hover:bg-yellow-600 text-white" + : "bg-[#2BACBE] hover:bg-[#2099A8] text-white" + }`} > {isRestPaused ? ( <> @@ -937,11 +960,10 @@ export const Exercise = () => { setIsPlaying(false) } }} - className={`flex-1 font-bold py-3 px-4 rounded-xl transition-all duration-300 transform hover:scale-105 flex items-center justify-center space-x-2 cursor-pointer ${ - isPlaying - ? "bg-yellow-500 hover:bg-yellow-600 text-white shadow-lg" - : "bg-[#2BACBE] hover:bg-[#2099A8] text-white shadow-lg" - }`} + className={`flex-1 font-bold py-3 px-4 rounded-xl transition-all duration-300 transform hover:scale-105 flex items-center justify-center space-x-2 cursor-pointer ${isPlaying + ? "bg-yellow-500 hover:bg-yellow-600 text-white shadow-lg" + : "bg-[#2BACBE] hover:bg-[#2099A8] text-white shadow-lg" + }`} > {isPlaying ? ( <> diff --git a/src/pages/ExerciseOld.tsx b/src/pages/ExerciseOld.tsx new file mode 100644 index 0000000..a2d6dba --- /dev/null +++ b/src/pages/ExerciseOld.tsx @@ -0,0 +1,1025 @@ +"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 // Название тренировочной сессии +} + +// Описываем параметры, которые приходят из URL страницы +interface RouteParams { + courseId: string // ID курса (программы тренировок) + exerciseId: string // ID конкретного упражнения +} + +export const Exercise = () => { + // Получаем функции для навигации и параметры из URL + const history = useHistory() + const { courseId, exerciseId } = useParams() + + // ========== ОСНОВНЫЕ СОСТОЯНИЯ КОМПОНЕНТА ========== + // Состояние для хранения данных упражнения (null = данные еще не загружены) + // Мы говорим TypeScript, что переменная exercise может быть либо объектом типа Exercise, либо null. Это важно, потому что изначально у нас нет данных (например, мы их ещё не загрузили), и состояние пустое — null + + + // exercise — это переменная/ текущее значение состояния, которое может быть объектом Exercise или null. + // setExercise — функция, с помощью которой можно обновить exercise. + + + // useState("") — это вызов функции useState с аргументом "" (пустая строка). + // Внутри угловых скобок мы указываем тип состояния (TypeScript). + // В круглых скобках ("") — передаём начальное значение состояния. + + const [exercise, setExercise] = useState(null) + // useState(null) — это вызов функции useState с начальным значением null + + // Состояние загрузки (true = идет загрузка, false = загрузка завершена) + const [loading, setLoading] = useState(true) + // если TypeScript может сам вывести тип из переданного начального значения, то указывать тип явно не обязательно + + // Состояние ошибки (пустая строка = нет ошибки, текст = описание ошибки) + // С помощью ДЕСТРУКТУРИЗАЦИИ МАССИВА мы присваиваем первый элемент массива переменной error, а второй — функции setError + // Деструктуризация массива — это удобный синтаксис в JavaScript/TypeScript, который позволяет распаковать значения из массива в отдельные переменные + // Когда начальное значение — null или undefined, и TypeScript не может понять, какого типа будет состояние. + 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) + + // Массив номеров завершенных подходов (например: [1, 2] = завершены 1-й и 2-й подходы) + const [completedSets, setCompletedSets] = useState([]) + + // ========== СОСТОЯНИЯ ИНТЕРФЕЙСА ========== + // Крутится ли иконка обновления (для анимации) + 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 loadProgress = async () => { + const serverLoaded = await loadProgressFromServer() + + // async — говорит, что функция работает асинхронно и возвращает промис. + // await — заставляет функцию ждать завершения промиса, не блокируя при этом весь поток выполнения. + + + if (!serverLoaded) { + // Fallback to localStorage if server loading failed + console.log("Пытаемся загрузить прогресс из localStorage как резервный вариант") + + const savedProgress = localStorage.getItem(`exerciseProgress_${courseId}_${exerciseId}`) + + if (savedProgress) { + // Если прогресс найден, преобразуем его из текста в объект + const progress = JSON.parse(savedProgress) + + // Проверяем статус сохраненного прогресса + 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) // Упражнение не завершено + } + } + } + } + + loadProgress() + }, [courseId, exerciseId, totalTime, totalSets]) // Зависимости для повторного выполнения + + // ========== ЗАГРУЗКА ДАННЫХ УПРАЖНЕНИЯ С СЕРВЕРА ========== + useEffect(() => { + 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, + 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) { + const timeInSeconds = Number.parseInt(exerciseData.time) * 60 + setTotalTime(timeInSeconds) + console.log("Установлено время упражнения:", timeInSeconds, "секунд") + } + + // Если есть количество подходов, устанавливаем его + if (exerciseData.count) { + setTotalSets(exerciseData.count) + console.log("Установлено количество подходов:", exerciseData.count) + } + + // Загрузка завершена успешно + 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]) // Код выполняется при изменении ID курса или упражнения + + + + // ========== ФУНКЦИЯ ЗАГРУЗКИ ПРОГРЕССА С СЕРВЕРА ========== + // Эта функция получает сохраненный прогресс упражнения с бэкенда + const loadProgressFromServer = async (): Promise => { + try { + console.log("Загружаем сохраненный прогресс с сервера для упражнения:", exerciseId, "в курсе:", courseId) + + const response = await connect.get(`pacient/${courseId}/${exerciseId}/`) + + const url = `pacient/${courseId}/${exerciseId}`; + console.log("Запрос по URL:", url); + + console.log("ФУНКЦИЯ ЗАГРУЗКИ ПРОГРЕССА С СЕРВЕРА", response.data) + + 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) // Сбрасываем флаг сохранения + } + + return true // Successfully loaded from server + } else { + console.log("Прогресс на сервере не найден, начинаем с чистого листа") + return false // No progress found on server + } + } catch (error) { + console.error("Ошибка при загрузке прогресса с сервера:", error) + return false // Failed to load from server + } + } + + // ========== ФУНКЦИЯ КОНВЕРТАЦИИ ВРЕМЕНИ ИЗ 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; + + console.log(`!!!!Текущее упражнение: ${currentExerciseNum}, следующее: ${nextExerciseId}`) + + console.log({ courseId }, { nextExerciseId }) + // Переходим к следующему упражнению в том же курсе + history.push(`${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, + ) => { + console.log("Сохраняем прогресс на паузе для курса", courseId, "упражнения", exerciseId) + + // Сохраняем как незавершенное (СТАТУС 0 = В ПРОЦЕССЕ/НА ПАУЗЕ) + await saveSetProgress(setNumber, formatTime(currentTime), 0) + + // Сохраняем прогресс в память браузера + localStorage.setItem( + `exerciseProgress_${courseId}_${exerciseId}`, + JSON.stringify({ + exerciseId: exercise?.id, // ID упражнения + courseId: courseId, // ID курса для дополнительной проверки + position: currentTime, // Текущая позиция времени + set: setNumber, // Текущий подход + status: 0, // СТАТУС 0 = УПРАЖНЕНИЕ НА ПАУЗЕ ИЛИ В ПРОЦЕССЕ + completedSets: completedSetsArray, // Завершенные подходы + isResting: resting, // Идет ли отдых + restTime: currentRestTime, // Оставшееся время отдыха + isRestPaused: restPaused, // На паузе ли отдых + }), + ) + + // Помечаем что прогресс сохранен + setHasSavedProgress(true) + } + + // ========== ОСНОВНОЙ ТАЙМЕР ДЛЯ УПРАЖНЕНИЯ ========== + // Этот код выполняется каждую секунду когда упражнение активно + 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} секунд`) + + // Проверяем, достигли ли мы конца времени для текущего подхода + if (newTime >= totalTime) { + console.log("Время подхода истекло, автоматически завершаем подход") + setIsPlaying(false) + + // Используем setTimeout чтобы избежать проблем с состоянием в useEffect + setTimeout(() => { + handleCompleteSet() + }, 100) + return totalTime + } + return newTime + }) + }, 1000) // Выполняется каждую секунду (1000 миллисекунд) + } + + // Очищаем таймер при размонтировании компонента или изменении зависимостей + return () => { + if (interval) { + console.log("Останавливаем таймер упражнения") + clearInterval(interval) + } + } + }, [isPlaying, totalTime, isCompleted, isResting, currentSet, completedSets]) + + // ========== ТАЙМЕР ДЛЯ ОТДЫХА МЕЖДУ ПОДХОДАМИ ========== + // Этот код выполняется каждую секунду во время отдыха + useEffect(() => { + let restInterval: NodeJS.Timeout | undefined + + // Таймер отдыха работает только если: + // - идет отдых (isResting = true) + // - есть оставшееся время отдыха (restTime > 0) + // - отдых не на паузе (isRestPaused = false) + if (isResting && restTime > 0 && !isRestPaused) { + console.log("Запускаем таймер отдыха") + + restInterval = setInterval(() => { + setRestTime((prev) => { + const newRestTime = prev - 1 + console.log(`Таймер отдыха: ${newRestTime} секунд осталось`) + + // Если время отдыха закончилось + if (newRestTime <= 0) { + console.log("Отдых закончен, переходим к следующему подходу") + + // Выключаем режим отдыха и переходим к следующему подходу + setIsResting(false) // Выключаем отдых + setIsRestPaused(false) // Убираем паузу + setCurrentSet(currentSet + 1) // Переходим к следующему подходу + setCurrentTime(0) // Сбрасываем время упражнения + return 0 + } + return newRestTime + }) + }, 1000) // Выполняется каждую секунду + } + + // Очищаем таймер при размонтировании компонента или изменении зависимостей + 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 // Получаем оставшиеся секунды + // Добавляем ведущий ноль если число меньше 10 (например: 5 -> "05") + return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}` + } + + // ========== РАСЧЕТ ПРОГРЕССА ДЛЯ ПОЛОСКИ ========== + // Вычисляем процент заполнения полоски прогресса (от 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: "Описание упражнения", + description: exercise?.desc || "Выполните упражнение согласно инструкции.", + }, + { + title: "Продолжительность", + description: `Время выполнения: ${exercise?.time || 15} минут`, + }, + { + title: "Подходы и повторения", + description: `Выполните ${exercise?.count || 1} подход по ${exercise?.repeats || 12} повторений с отдыхом 60 секунд между подходами.`, + }, + { + title: "Позиция в программе", + description: `Это упражнение №${exercise?.position || 1} в программе дня ${exercise?.day || 1}.`, + }, + { + title: "Техника безопасности", + description: "Следите за правильной техникой выполнения. При появлении боли немедленно прекратите упражнение.", + }, + ] + + // ========== ЭКРАНЫ ЗАГРУЗКИ И ОШИБОК ========== + + // Если данные еще загружаются, показываем экран загрузки + if (loading) { + return ( +
+
+
+
+

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

+
+
+
+ ) + } + + // Если произошла ошибка, показываем экран ошибки + if (error) { + return ( +
+
+ +
+
+

{error}

+ +
+
+
+
+ ) + } + + // Если упражнение не найдено, показываем соответствующее сообщение + if (!exercise) { + return ( +
+
+ +
+
+

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

+ +
+
+
+
+ ) + } + + // ========== ОСНОВНОЙ ИНТЕРФЕЙС УПРАЖНЕНИЯ ========== + return ( +
+
+ {/* Заголовок страницы с названием упражнения */} + + +
+ {/* Основная карточка с изображением и кнопкой воспроизведения */} +
+
+ {/* Изображение упражнения */} + {exercise.title} { + // Если изображение не загрузилось, показываем заглушку + const target = e.target as HTMLImageElement + target.src = "/placeholder.svg?height=300&width=400&text=Упражнение" + }} + /> + {/* Градиент поверх изображения для лучшей читаемости */} +
+ + {/* Центральная кнопка управления */} +
+ {isCompleted ? ( + // Если упражнение завершено - показываем галочку +
+ +
+ ) : isResting ? ( + // Если идет отдых - показываем иконку часов +
+ +
+ ) : ( + // Если упражнение активно - показываем кнопку воспроизведения/паузы + + )} +
+ + {/* Индикаторы статуса в левом верхнем углу */} + {isCompleted && ( +
+
+ + Выполнено + +
+ )} + + {isResting && ( +
+
+ + {isRestPaused ? "Отдых на паузе" : "Отдых"} + +
+ )} + + {isPlaying && !isCompleted && !isResting && ( +
+
+ + Выполнение + +
+ )} + + {/* Таймер в правом верхнем углу */} +
+ + {isResting ? formatTime(restTime) : formatTime(currentTime)} + +
+
+
+
+ + {/* Индикатор прогресса подходов */} +
+
+

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

+
+ {/* Создаем полоски для каждого подхода */} + {Array.from({ length: totalSets }, (_, index) => { + const setNumber = index + 1 // Номер подхода (начинаем с 1) + const isSetCompleted = completedSets.includes(setNumber) // Завершен ли этот подход + const isCurrent = setNumber === currentSet && !isSetCompleted // Текущий ли это подход + + return ( +
+ {/* Полоска прогресса для подхода */} +
+ {/* Номер подхода под полоской */} +
{setNumber}
+
+ ) + })} +
+ {/* Общая статистика */} +
+ Завершено: {completedSets.length} + Всего: {totalSets} +
+
+
+ + {/* Информационные карточки об упражнении */} +
+ {exerciseSteps.map((step, index) => ( +
+
+
+

{step.title}

+

{step.description}

+
+
+
+ ))} +
+ + {/* Фиксированная панель управления внизу экрана */} +
+
+ {/* Информация о текущем состоянии */} +
+
+ {/* Индикатор активности */} +
+ {/* Текст состояния */} + + {isCompleted + ? `Все подходы завершены!` + : isResting + ? `Отдых перед подходом ${currentSet + 1}${isRestPaused ? " (пауза)" : ""}` + : `Подход ${currentSet} из ${totalSets}`} + +
+ {/* Отображение времени */} + + {isResting ? `${formatTime(restTime)} отдых` : `${formatTime(currentTime)} / ${formatTime(totalTime)}`} + +
+ + {/* Полоска прогресса */} +
+
+
+ + {/* Кнопки управления */} +
+ {isCompleted ? ( + // Если упражнение завершено - показываем кнопки перехода и статуса + <> + +
+ + Завершено +
+ + ) : isResting ? ( + // Если идет отдых - показываем кнопки управления отдыхом + <> + + + + ) : ( + // Если упражнение активно - показываем основные кнопки управления + <> + + + {/* Кнопка завершения текущего подхода */} + + + )} + + {/* Кнопка сброса (всегда доступна) */} + +
+
+
+ + {/* Нижняя навигация */} + +
+
+ ) +}