385 lines
14 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"
import { useState, useEffect } from "react"
import { useHistory, useParams } from "react-router-dom"
import BottomNavigation from "../components/BottomNavigation"
import { getRouteCourseComplete } from "../shared/consts/router"
import HeaderNav from "../components/HeaderNav"
import video from "../assets/img/video.mov"
import { connect } from "../confconnect"
export interface Exercise {
id: number
title: string
desc: string
url_file: string
url_file_img: string
time: string
repeats: number
count: number
position: number
day: number
sessionname: string
}
interface RouteParams {
courseId: string
exerciseId: string
}
export const Exercise = () => {
const history = useHistory()
const { courseId, exerciseId } = useParams<RouteParams>()
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) // Default 15 minutes
const [currentSet, setCurrentSet] = useState(1)
const [totalSets, setTotalSets] = useState(3)
useEffect(() => {
console.log("Course ID:", courseId)
console.log("Exercise ID:", exerciseId)
if (!courseId || !exerciseId) {
setError("ID курса или упражнения не найден")
setLoading(false)
return
}
// Получаем данные упражнения через API: GET /pacient/:course_id/:exercise_id
connect
.get(`pacient/${courseId}/${exerciseId}`)
.then((response) => {
console.log("Response status:", response.status)
console.log("Response data:", response.data)
const exerciseData = response.data
setExercise({
id: exerciseData.id,
title: exerciseData.title,
desc: exerciseData.desc,
url_file: exerciseData.url_file,
url_file_img: exerciseData.url_file_img,
time: exerciseData.time,
repeats: exerciseData.repeats,
count: exerciseData.count,
position: exerciseData.position,
day: exerciseData.day,
sessionname: exerciseData.sessionname,
})
// Устанавливаем время и подходы из данных упражнения
if (exerciseData.time) {
setTotalTime(Number.parseInt(exerciseData.time) * 60) // Конвертируем минуты в секунды
}
if (exerciseData.count) {
setTotalSets(exerciseData.count)
}
setLoading(false)
})
.catch((error) => {
console.error("Ошибка при получении упражнения:", error)
if (error.response) {
console.error("Ошибка ответа сервера:", error.response.status, error.response.data)
setError(`Ошибка сервера: ${error.response.status}`)
} else if (error.request) {
console.error("Нет ответа от сервера:", error.request)
setError("Нет ответа от сервера")
} else {
console.error("Ошибка при настройке запроса:", error.message)
setError(`Ошибка: ${error.message}`)
}
setLoading(false)
})
}, [courseId, exerciseId])
useEffect(() => {
let interval: NodeJS.Timeout | undefined
if (isPlaying) {
interval = setInterval(() => {
setCurrentTime((prev) => {
if (prev >= totalTime) {
setIsPlaying(false)
// Отправляем результат на сервер при завершении
submitProgress()
// Show completion animation
history.push(getRouteCourseComplete())
return totalTime
}
return prev + 1
})
}, 1000)
}
return () => {
if (interval) clearInterval(interval)
}
}, [isPlaying, totalTime, history])
// Функция для отправки прогресса на сервер
const submitProgress = async () => {
if (!courseId || !exerciseId) return
try {
const timeUsers = formatTime(currentTime) // Отправляем время в формате MM:SS
await connect.post(`pacient/${courseId}/${exerciseId}`, {
time_users: timeUsers,
})
console.log("Прогресс отправлен на сервер:", timeUsers)
} catch (error) {
console.error("Ошибка при отправке прогресса:", error)
}
}
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`
}
const progress = (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 RefreshIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#6F6F6F"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M2.5 2v6h6M21.5 22v-6h-6" />
<path d="M22 11.5A10 10 0 0 0 3.2 7.2M2 12.5a10 10 0 0 0 18.8 4.2" />
</svg>
)
const CheckIcon = () => (
<svg className="w-6 h-6 text-cyan-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</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 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 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.day}, позиция ${exercise.position}`} />
<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 || video} alt={exercise.title} className="w-full h-120 object-cover" />
<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">
<button
onClick={() => setIsPlaying(!isPlaying)}
className={
isPlaying
? "w-20 h-20 bg-transparent backdrop-blur-xl rounded-full flex items-center justify-center shadow-2xl transition-all duration-300 transform hover:scale-110 border border-cyan-50 opacity-40"
: "rounded-full w-20 h-20 flex items-center justify-center backdrop-blur-xl opacity-70"
}
>
{isPlaying ? <PauseIcon /> : <PlayIcon />}
</button>
</div>
{isPlaying && (
<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">{formatTime(currentTime)}</span>
</div>
</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>
{/* Fixed Timer at Bottom */}
<div className="fixed bottom-36 left-0 right-0 bg-white opacity-95 border-t border-white/20 px-4 sm:px-6 py-4 shadow-2xl 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 bg-green-400 rounded-full animate-pulse"></div>
<span className="text-sm font-bold text-gray-700">
Подход {currentSet} из {totalSets}
</span>
</div>
<span className="text-sm font-black text-gray-700">
{formatTime(currentTime)} / {formatTime(totalTime)}
</span>
</div>
<div className="bg-white/30 rounded-full h-3 mb-4 overflow-hidden">
<div
className="bg-gradient-to-r from-[#2BACBE] via-cyan-500 to-cyan-700 h-3 rounded-full transition-all duration-1000 shadow-sm"
style={{ width: `${progress}%` }}
></div>
</div>
<div className="flex space-x-3">
<button
onClick={() => setIsPlaying(!isPlaying)}
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 ${
isPlaying
? "bg-gray-400 text-white shadow-lg"
: "bg-[#2BACBE] hover:bg-[#2099A8] text-white shadow-lg"
}`}
>
{isPlaying ? (
<>
<PauseIcon /> <span>Пауза</span>
</>
) : (
<>
<PlayIcon /> <span className="backdrop-blur-xl">Начать</span>
</>
)}
</button>
<button
onClick={() => {
setCurrentTime(0)
setIsPlaying(false)
}}
className="px-6 py-3 bg-white text-gray-800 font-bold rounded-xl transition-all duration-300 hover:shadow-lg border border-gray-200 flex items-center justify-center"
>
<RefreshIcon />
</button>
<button
onClick={() => {
setCurrentSet((prev) => Math.min(prev + 1, totalSets))
submitProgress()
}}
className="px-6 py-3 bg-white text-gray-800 font-bold rounded-xl transition-all duration-300 hover:shadow-lg border border-gray-200 flex items-center justify-center"
>
<CheckIcon />
</button>
</div>
</div>
</div>
<BottomNavigation />
</div>
</div>
)
}