995 lines
40 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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<RouteParams>()
const location = useLocation()
const [course_exercises, setCourseExercises] = useState<CourseExercises[]>([])
const [actualExerciseId, setActualExerciseId] = useState<string>("")
const [currentDay, setCurrentDay] = useState<number | null>(null)
const [exercise, setExercise] = useState<Exercise | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string>("")
// ========== СОСТОЯНИЯ ТАЙМЕРУ УПРАЖНЕНИЯ ==========
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<number[]>([])
// ========== СОСТОЯНИЯ ИНТЕРФЕЙСА ==========
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 = () => (
<svg className="w-10 h-10 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z" />
</svg>
)
const PauseIcon = () => (
<svg className="w-10 h-10 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
</svg>
)
const CheckIcon = () => (
<svg className="w-10 h-10 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
</svg>
)
const RestIcon = () => (
<svg width="40" height="40" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 2C17.523 2 22 6.477 22 12C22 17.523 17.523 22 12 22C6.477 22 2 17.523 2 12C2 6.477 6.477 2 12 2ZM12 6C11.7348 6 11.4804 6.10536 11.2929 6.29289C11.1054 6.48043 11 6.73478 11 7V12C11.0001 12.2652 11.1055 12.5195 11.293 12.707L14.293 15.707C14.4816 15.8892 14.7342 15.99 14.9964 15.9877C15.2586 15.9854 15.5094 15.8802 15.6948 15.6948C15.8802 15.5094 15.9854 15.2586 15.9877 14.9964C15.99 14.7342 14.8892 14.4816 14.293 14.293L13 11.586V7C13 6.73478 12.8946 6.48043 12.7071 6.29289C12.5196 6.10536 12.2652 6 12 6Z"
fill="#ffffff95"
/>
</svg>
)
// ========== ИНФОРМАЦИЯ ОБ УПРАЖНЕНИИ ДЛЯ ОТОБРАЖЕНИЯ ==========
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 (
<div className="bg-gray-50 w-full h-full overflow-auto">
<div className="mt-36 min-h-screen max-w-4xl mx-auto flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#2BACBE] mx-auto mb-4"></div>
<p className="text-gray-600">Загрузка упражнения...</p>
</div>
</div>
</div>
)
}
if (error) {
return (
<div className="bg-gray-50 w-full h-full overflow-auto">
<div className="mt-36 mb-90 min-h-screen max-w-4xl mx-auto">
<HeaderNav item="Ошибка" text="Не удалось загрузить" />
<div className="px-6 mt-8">
<div className="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
<p className="text-red-600 font-medium mb-4">{error}</p>
<button
onClick={() => history.goBack()}
className="bg-[#2BACBE] text-white px-4 py-2 rounded-lg hover:bg-[#2A9FB8] transition-colors"
>
Вернуться назад
</button>
</div>
</div>
</div>
</div>
)
}
if (!exercise) {
return (
<div className="bg-gray-50 w-full h-full overflow-auto">
<div className="mt-36 mb-90 min-h-screen max-w-4xl mx-auto">
<HeaderNav item="Упражнение" text="Не найдено" />
<div className="px-6 mt-8">
<div className="bg-gray-50 border border-gray-200 rounded-lg p-8 text-center">
<p className="text-gray-600 mb-4">Упражнение не найдено</p>
<button
onClick={() => history.goBack()}
className="bg-[#2BACBE] text-white px-4 py-2 rounded-lg hover:bg-[#2A9FB8] transition-colors"
>
Вернуться назад
</button>
</div>
</div>
</div>
</div>
)
}
// ========== ОСНОВНОЙ ИНТЕРФЕЙС УПРАЖНЕНИЯ ==========
return (
<div className="bg-gray-50 w-full h-full overflow-auto">
<div className="mt-36 mb-90 min-h-screen max-w-4xl mx-auto">
<HeaderNav item={exercise.title} text={`упражнение ${exercise.position} / день ${currentDay}`} />
<div className="px-4 sm:px-6 mt-10 mb-6">
<div className="glass-morphism rounded-3xl overflow-hidden shadow-2xl border border-white/20 backdrop-blur-2xl">
<div className="relative">
<img
src={exercise.url_file_img || "/placeholder.svg?height=300&width=400&text=Упражнение"}
alt={exercise.title}
className="w-full h-96 object-cover"
onError={(e) => {
const target = e.target as HTMLImageElement
target.src = "/placeholder.svg?height=300&width=400&text=Упражнение"
}}
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/30 via-transparent to-black/10"></div>
<div className="absolute inset-0 flex items-center justify-center">
{isCompleted ? (
<div className="w-20 h-20 bg-cyan-500 rounded-full flex items-center justify-center shadow-2xl">
<CheckIcon />
</div>
) : isResting ? (
<div className="w-20 h-20 bg-cyan-500 opacity-80 rounded-full flex items-center justify-center shadow-2xl">
<RestIcon />
</div>
) : (
<button
onClick={() => !isCompleted && !isResting && setIsPlaying(!isPlaying)}
disabled={isCompleted || isResting}
className={`w-20 h-20 rounded-full flex items-center justify-center shadow-2xl transition-all duration-300 transform hover:scale-110 ${
isPlaying
? "bg-white/20 backdrop-blur-xl border border-white/30"
: "bg-white/30 backdrop-blur-xl border border-white/50"
} ${isCompleted || isResting ? "opacity-50 cursor-not-allowed" : ""}`}
>
{isPlaying ? <PauseIcon /> : <PlayIcon />}
</button>
)}
</div>
{isCompleted && (
<div className="absolute top-4 left-4 flex items-center space-x-2">
<div className="w-3 h-3 bg-cyan-500 rounded-full"></div>
<span className="text-white text-sm font-bold bg-cyan-600/80 px-3 py-1 rounded-full backdrop-blur-sm">
Выполнено
</span>
</div>
)}
{isResting && (
<div className="absolute top-4 left-4 flex items-center space-x-2">
<div className={`w-3 h-3 bg-cyan-500 rounded-full ${!isRestPaused ? "animate-pulse" : ""}`}></div>
<span className="text-white text-sm font-bold bg-cyan-600/80 px-3 py-1 rounded-full backdrop-blur-sm">
{isRestPaused ? "Отдых на паузе" : "Отдых"}
</span>
</div>
)}
{isPlaying && !isCompleted && !isResting && (
<div className="absolute top-4 left-4 flex items-center space-x-2">
<div className="w-3 h-3 bg-orange-500 rounded-full animate-pulse"></div>
<span className="text-white text-sm font-bold bg-black/30 px-3 py-1 rounded-full backdrop-blur-sm">
Выполнение
</span>
</div>
)}
<div className="absolute top-4 right-4 bg-cyan-50 backdrop-blur-sm px-3 py-1 rounded-xl">
<span className="text-gray-800 text-sm font-bold">
{isResting ? formatTime(restTime) : formatTime(currentTime)}
</span>
</div>
</div>
</div>
</div>
<div className="px-4 sm:px-6 mb-6">
<div className="bg-white rounded-2xl p-4 border border-gray-200 shadow-lg">
<h3 className="text-lg font-bold text-gray-800 mb-3">Прогресс подходов</h3>
<div className="flex space-x-2">
{Array.from({ length: totalSets }, (_, index) => {
const setNumber = index + 1
const isSetCompleted = completedSets.includes(setNumber)
const isCurrent = setNumber === currentSet && !isSetCompleted
return (
<div key={setNumber} className="flex-1 text-center">
<div
className={`h-3 rounded-full transition-all duration-300 ${
isSetCompleted ? "bg-cyan-500" : isCurrent ? "bg-cyan-500" : "bg-gray-200"
}`}
/>
<div className="text-xs font-bold mt-1 text-gray-600">{setNumber}</div>
</div>
)
})}
</div>
<div className="flex justify-between text-xs text-gray-600 mt-2">
<span>Завершено: {completedSets.length}</span>
<span>Всего: {totalSets}</span>
</div>
</div>
</div>
<div className="px-4 sm:px-6 space-y-4 mb-6">
{exerciseSteps.map((step, index) => (
<div
key={index}
className="bg-white rounded-2xl p-5 border border-gray-200 shadow-lg hover:shadow-xl transition-all duration-300"
>
<div className="flex items-start space-x-4">
<div>
<h3 className="text-lg font-black text-gray-800 mb-2">{step.title}</h3>
<p className="text-gray-600 leading-relaxed text-sm sm:text-base">{step.description}</p>
</div>
</div>
</div>
))}
</div>
<div className="fixed bottom-36 left-0 right-0 bg-white/95 backdrop-blur-sm border-t border-gray-200 px-4 sm:px-6 py-4 shadow-xl z-30">
<div className="max-w-md mx-auto">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center space-x-2">
<div
className={`w-2 h-2 rounded-full ${
isCompleted
? "bg-cyan-400"
: isResting
? `bg-cyan-400 ${!isRestPaused ? "animate-pulse" : ""}`
: "bg-cyan-400 animate-pulse"
}`}
></div>
<span className="text-sm font-bold text-gray-700">
{isCompleted
? `Все подходы завершены!`
: isResting
? `Отдых перед подходом ${currentSet + 1}${isRestPaused ? " (пауза)" : ""}`
: `Подход ${currentSet} из ${totalSets}`}
</span>
</div>
<span className="text-sm font-black text-gray-700">
{isResting ? `${formatTime(restTime)} отдых` : `${formatTime(currentTime)} / ${formatTime(totalTime)}`}
</span>
</div>
<div className="bg-gray-200 rounded-full h-2 mb-4 overflow-hidden">
<div
className={`h-2 rounded-full transition-all duration-1000 shadow-sm ${
isCompleted
? "bg-gradient-to-r from-cyan-400 via-cyan-500 to-cyan-600"
: isResting
? "bg-gradient-to-r from-cyan-400 via-cyan-500 to-cyan-600"
: "bg-gradient-to-r from-[#2BACBE] via-cyan-500 to-cyan-700"
}`}
style={{ width: `${progress}%` }}
></div>
</div>
<div className="flex space-x-3">
{isCompleted ? (
<>
<button
onClick={goToNextExercise}
className="flex-1 font-bold py-3 px-4 rounded-xl bg-orange-400 hover:bg-yellow-500 hover:scale-105 text-white flex items-center justify-center space-x-2 transition-all duration-300"
>
<span>Следующее упражнение</span>
</button>
<div className="px-4 py-3 bg-cyan-500 text-white font-bold rounded-xl flex items-center justify-center space-x-2">
<CheckIcon />
<span>Завершено</span>
</div>
</>
) : isResting ? (
<>
<button
onClick={() => {
setIsRestPaused(!isRestPaused)
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"
}`}
>
{isRestPaused ? (
<>
<PlayIcon />
<span>Продолжить отдых</span>
</>
) : (
<>
<PauseIcon />
<span>Пауза отдыха</span>
</>
)}
</button>
<button
onClick={() => {
console.log("Пропускаем отдых, переходим к следующему подходу")
setIsResting(false)
setIsRestPaused(false)
setCurrentSet(currentSet + 1)
setCurrentTime(0)
}}
className="px-6 py-3 bg-gray-500 hover:bg-gray-600 text-white font-bold rounded-xl transition-all duration-300 flex items-center justify-center"
>
Пропустить
</button>
</>
) : (
<>
<button
onClick={() => {
if (!isPlaying) {
console.log("Запускаем упражнение")
setIsPlaying(true)
} else {
console.log("Ставим упражнение на паузу")
handlePause()
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"
}`}
>
{isPlaying ? (
<>
<PauseIcon /> <span>Пауза</span>
</>
) : (
<>
<PlayIcon />
<span className="backdrop-blur-xl">{hasSavedProgress ? "Продолжить" : "Начать"}</span>
</>
)}
</button>
<button
onClick={handleCompleteSet}
className="px-6 py-3 bg-cyan-500 hover:bg-cyan-600 hover:scale-110 text-white font-bold rounded-xl transition-all duration-300 flex items-center justify-center"
>
<CheckIcon />
</button>
</>
)}
<button
onClick={() => {
console.log("Сбрасываем все состояния упражнения")
setCurrentTime(0)
setIsPlaying(false)
setHasSavedProgress(false)
setIsCompleted(false)
setCompletedSets([])
setCurrentSet(1)
setIsResting(false)
setIsRestPaused(false)
setRestTime(0)
handleClick()
const storageKey = currentDay
? `exerciseProgress_${courseId}_${actualExerciseId}_day_${currentDay}`
: `exerciseProgress_${courseId}_${actualExerciseId}`
localStorage.removeItem(storageKey)
}}
className="cursor-pointer px-6 py-3 bg-gray-300 hover:bg-gray-200 text-white font-bold rounded-xl transition-all duration-300 hover:shadow-lg hover:scale-110 flex items-center justify-center"
>
<div onTransitionEnd={handleTransitionEnd} className="inline-block">
<RefreshIcon
className={`transition-transform duration-400 ease-in-out ${isRotating ? "rotate-360" : ""}`}
/>
</div>
</button>
</div>
</div>
</div>
<BottomNavigation />
</div>
</div>
)
}