"use client" // Импортируем необходимые функции из React для создания интерактивного компонента import { useState, useEffect } from "react" // Импортируем функции для навигации между страницами import { useHistory, useParams, useLocation } from "react-router-dom" // Импортируем иконки и компоненты интерфейса import { RefreshIcon } from "../components/icons/RefreshIcon" import HeaderNav from "../components/HeaderNav" import BottomNavigation from "../components/BottomNavigation" import type { CourseExercises } from "./CourseExercises" // Импортируем функцию для связи с сервером (бэкендом) 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 // Название тренировочной сессии } interface RouteParams { courseId: string exerciseId?: string // Made optional since we might have exerciseIndex instead exerciseIndex?: string // Added exerciseIndex parameter } export const Exercise = () => { const history = useHistory() const { courseId, exerciseId, exerciseIndex } = useParams() const location = useLocation() const [course_exercises, setCourseExercises] = useState([]) const [actualExerciseId, setActualExerciseId] = useState("") const [currentDay, setCurrentDay] = useState(null) 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, setTotalTime] = useState(900) // ========== СОСТОЯНИЯ ПОДХОДОВ (СЕТОВ) ========== const [currentSet, setCurrentSet] = useState(1) const [totalSets, setTotalSets] = useState(3) const [completedSets, setCompletedSets] = useState([]) // ========== СОСТОЯНИЯ ИНТЕРФЕЙСА ========== const [isRotating, setIsRotating] = useState(false) const [hasSavedProgress, setHasSavedProgress] = useState(false) const [isCompleted, setIsCompleted] = useState(false) // ========== СОСТОЯНИЯ ОТДЫХА МЕЖДУ ПОДХОДАМИ ========== const [isResting, setIsResting] = useState(false) const [restTime, setRestTime] = useState(0) const [totalRestTime] = useState(60) const [isRestPaused, setIsRestPaused] = useState(false) useEffect(() => { const loadCourseExercises = async () => { if (!courseId) return try { const token = localStorage.getItem("authToken") const response = await connect.get(`/pacient/${courseId}`, { headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }) const exercises = response.data.course_exercises || [] setCourseExercises(exercises) const selectedDay = new URLSearchParams(location.search).get("day") const fixedDay = selectedDay ? Number.parseInt(selectedDay) : 1 setCurrentDay(fixedDay) console.log(" FIXED currentDay to:", fixedDay, "from URL parameter") if (exerciseIndex !== undefined) { let filteredExercises = exercises if (selectedDay) { filteredExercises = exercises.filter((ex: any) => ex.day === Number.parseInt(selectedDay)) } const exerciseIndexNum = Number.parseInt(exerciseIndex) if (exerciseIndexNum >= 0 && exerciseIndexNum < filteredExercises.length) { const targetExercise = filteredExercises[exerciseIndexNum] setActualExerciseId(targetExercise.id_exercise.toString()) console.log("[v0] Using exercise:", targetExercise.id_exercise, "but keeping fixed day:", fixedDay) } else { setError("Упражнение не найдено") setLoading(false) return } } else if (exerciseId) { setActualExerciseId(exerciseId) console.log("[v0] Using exerciseId:", exerciseId, "but keeping fixed day:", fixedDay) } else { setError("ID упражнения не найден") setLoading(false) return } } catch (error) { console.error("Ошибка при загрузке упражнений курса:", error) setError("Ошибка при загрузке упражнений") setLoading(false) } } loadCourseExercises() }, [courseId, exerciseId, exerciseIndex, location.search]) // ========== ЗАГРУЗКА ДАННЫХ УПРАЖНЕНИЯ С СЕРВЕРА ========== useEffect(() => { if (!actualExerciseId || !courseId) return console.log("Загружаем упражнение. ID курса:", courseId, "ID упражнения:", actualExerciseId) connect .get(`pacient/${courseId}/${actualExerciseId}`) .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) { 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, actualExerciseId]) // ========== ФУНКЦИЯ ЗАГРУЗКИ ПРОГРЕССА С СЕРВЕРА ========== const loadProgressFromServer = async () => { try { console.log(`Загружаем прогресс для курса ${courseId}, упражнения ${actualExerciseId}, день ${currentDay}`) const token = localStorage.getItem("authToken") const dayParam = currentDay ? `?day=${currentDay}` : "" const response = await connect.get(`pacient/${courseId}/${actualExerciseId}${dayParam}`, { headers: { Authorization: `Bearer ${token}`, }, }) console.log("Ответ сервера с прогрессом:", response.data) 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 const filteredProgress = progressArray.filter((record: any) => { const matchesCourse = record.course_id === Number.parseInt(courseId) const matchesDay = currentDay ? record.day === currentDay : true console.log( "[v0] Filtering progress - Course match:", matchesCourse, "Day match:", matchesDay, "Record day:", record.day, "Current day:", currentDay, ) return matchesCourse && matchesDay }) console.log("Отфильтрованный прогресс по дню:", filteredProgress) const completedSetsFromServer: number[] = [] let lastIncompleteSet: any = null let lastSavedTime = "00:00" 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) { setCompletedSets(completedSetsFromServer) } 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 В СЕКУНДЫ ========== 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 { const dayToSave = currentDay || 1 console.log(`[v0] SAVING PROGRESS: Set ${setNumber}, Status ${status}, Day ${dayToSave}`) console.log(`[v0] Current state - currentDay: ${currentDay}, dayToSave: ${dayToSave}`) console.log(`[v0] URL search params:`, location.search) const progressData = { time_users: timeString, set: setNumber, status: status, course_id: Number.parseInt(courseId), day: dayToSave, } console.log("FINAL DATA TO SERVER:", progressData) const response = await connect.post(`pacient/${courseId}/${actualExerciseId}`, progressData) if (response.status === 200 || response.status === 201) { console.log("SUCCESS: Progress saved to server for day", dayToSave) } } catch (error) { console.error("Ошибка при сохранении прогресса на сервер:", error) } } console.log("TEST DAY COMPLETE", course_exercises) console.log("Всего упражнений в курсе", course_exercises.length) // ========== ФУНКЦИЯ перехода к след упражнению ========== const goToNextExercise = () => { console.log("Переходим к следующему упражнению") if (exerciseIndex !== undefined) { const currentIndex = Number.parseInt(exerciseIndex) const nextIndex = currentIndex + 1 const selectedDay = new URLSearchParams(location.search).get("day") const dayParam = selectedDay ? `?day=${selectedDay}` : "" history.push(`/course/${courseId}/exercise/${nextIndex}${dayParam}`) } else { const currentExerciseNum = Number.parseInt(actualExerciseId) const nextExerciseId = currentExerciseNum + 1 history.push(`/course/${courseId}/${nextExerciseId}`) } } // ========== ФУНКЦИЯ ЗАВЕРШЕНИЯ ТЕКУЩЕГО ПОДХОДА ========== const handleCompleteSet = async () => { console.log("Пользователь завершает подход", currentSet, "из", totalSets) 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("УПРАЖНЕНИЕ ПОЛНОСТЬЮ ЗАВЕРШЕНО! Все подходы выполнены.") const storageKey = currentDay ? `exerciseProgress_${courseId}_${actualExerciseId}_day_${currentDay}` : `exerciseProgress_${courseId}_${actualExerciseId}` localStorage.setItem( storageKey, JSON.stringify({ exerciseId: exercise?.id, courseId: courseId, day: currentDay, position: totalTime, set: totalSets, status: 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, "упражнения", actualExerciseId, "день", currentDay) await saveSetProgress(setNumber, formatTime(currentTime), 0) const storageKey = currentDay ? `exerciseProgress_${courseId}_${actualExerciseId}_day_${currentDay}` : `exerciseProgress_${courseId}_${actualExerciseId}` localStorage.setItem( storageKey, JSON.stringify({ exerciseId: exercise?.id, courseId: courseId, day: currentDay, position: currentTime, set: setNumber, status: 0, completedSets: completedSetsArray, isResting: resting, restTime: currentRestTime, isRestPaused: restPaused, }), ) setHasSavedProgress(true) } // ========== ВОССТАНОВЛЕНИЕ ПРОГРЕССА ПРИ ЗАГРУЗКЕ СТРАНИЦЫ ========== useEffect(() => { const loadProgress = async () => { if (!actualExerciseId) return const serverLoaded = await loadProgressFromServer() if (!serverLoaded) { console.log("Пытаемся загрузить прогресс из localStorage как резервный вариант") const storageKey = currentDay ? `exerciseProgress_${courseId}_${actualExerciseId}_day_${currentDay}` : `exerciseProgress_${courseId}_${actualExerciseId}` const savedProgress = localStorage.getItem(storageKey) if (savedProgress) { const progress = JSON.parse(savedProgress) if (currentDay && progress.day && progress.day !== currentDay) { console.log("Прогресс для другого дня, игнорируем") return } setExercise({ id: progress.exerciseId, title: "", desc: "", url_file: "", url_file_img: "", time: "", repeats: 0, count: 0, position: progress.position, day: progress.day, sessionname: "", }) setCurrentTime(progress.position) setCurrentSet(progress.set) setCompletedSets(progress.completedSets || []) setIsResting(progress.isResting || false) setRestTime(progress.restTime || 0) setIsRestPaused(progress.isRestPaused || false) setHasSavedProgress(true) setIsCompleted(progress.status === 1) } } } loadProgress() }, [courseId, actualExerciseId, currentDay, totalTime, totalSets]) // ========== ОСНОВНОЙ ТАЙМЕР ДЛЯ УПРАЖНЕНИЯ ========== useEffect(() => { let interval: NodeJS.Timeout | undefined 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(() => { handleCompleteSet() }, 100) return totalTime } return newTime }) }, 1000) } return () => { if (interval) { console.log("Останавливаем таймер упражнения") clearInterval(interval) } } }, [isPlaying, totalTime, isCompleted, isResting, currentSet, completedSets]) // ========== ТАЙМЕР ДЛЯ ОТДЫХА МЕЖДУ ПОДХОДАМИ ========== useEffect(() => { let restInterval: NodeJS.Timeout | undefined 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) } // ========== ФУНКЦИЯ ФОРМАТИРОВАНИЯ ВРЕМЕНИ ========== 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 = isResting ? ((totalRestTime - restTime) / totalRestTime) * 100 : (currentTime / totalTime) * 100 // ========== ИКОНКИ ДЛЯ КНОПОК ========== const PlayIcon = () => ( ) const PauseIcon = () => ( ) const CheckIcon = () => ( ) 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 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 ? ( <> ) : ( <> )}
) }