выведено id курса на страницу курса
This commit is contained in:
parent
c133acf150
commit
cdd62757fc
@ -5,8 +5,8 @@ import Login from "./pages/Login";
|
|||||||
import Home from "./pages/Home";
|
import Home from "./pages/Home";
|
||||||
import ForgotPasword from "./pages/ForgotPassword";
|
import ForgotPasword from "./pages/ForgotPassword";
|
||||||
import { Courses } from "./pages/Courses";
|
import { Courses } from "./pages/Courses";
|
||||||
import { CourseExercises } from "./pages/CourseExercises";
|
import CourseExercises from "./pages/CourseExercises";
|
||||||
import { Exercise } from "./pages/Exercise";
|
|
||||||
import Settings from "./pages/Settings";
|
import Settings from "./pages/Settings";
|
||||||
import CourseComplete from "./pages/CourseComplete";
|
import CourseComplete from "./pages/CourseComplete";
|
||||||
|
|
||||||
@ -15,8 +15,8 @@ import { getRouteLogin } from "./shared/consts/router";
|
|||||||
import { getRouteHome } from "./shared/consts/router";
|
import { getRouteHome } from "./shared/consts/router";
|
||||||
import { getRouteForgotPassword } from "./shared/consts/router";
|
import { getRouteForgotPassword } from "./shared/consts/router";
|
||||||
import { getRouteCourses } from "./shared/consts/router";
|
import { getRouteCourses } from "./shared/consts/router";
|
||||||
import { getRouteCourseExercises } from "./shared/consts/router";
|
|
||||||
import { getRouteExercise } from "./shared/consts/router";
|
|
||||||
import { getRouteSettings } from "./shared/consts/router";
|
import { getRouteSettings } from "./shared/consts/router";
|
||||||
import { getRouteCourseComplete } from "./shared/consts/router";
|
import { getRouteCourseComplete } from "./shared/consts/router";
|
||||||
|
|
||||||
@ -31,10 +31,14 @@ const AppRoutes = () => (
|
|||||||
<Route path={getRouteHome()} component={Home} />
|
<Route path={getRouteHome()} component={Home} />
|
||||||
<Route path={getRouteForgotPassword()} component={ForgotPasword} />
|
<Route path={getRouteForgotPassword()} component={ForgotPasword} />
|
||||||
<Route path={getRouteCourses()} component={Courses} />
|
<Route path={getRouteCourses()} component={Courses} />
|
||||||
<Route path={getRouteCourseExercises(':id')} component={CourseExercises} />
|
|
||||||
<Route path={getRouteExercise(':id')} component={Exercise} />
|
|
||||||
<Route path={getRouteSettings()} component={Settings} />
|
<Route path={getRouteSettings()} component={Settings} />
|
||||||
<Route path={getRouteCourseComplete()} component={CourseComplete} />
|
<Route path={getRouteCourseComplete()} component={CourseComplete} />
|
||||||
|
|
||||||
|
|
||||||
|
<Route path="/course/:id" component={CourseExercises} />
|
||||||
|
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState } from "react"
|
import type React from "react"
|
||||||
import { useHistory } from "react-router-dom"
|
import { useState, useEffect } from "react"
|
||||||
|
import { useHistory, useParams } from "react-router-dom"
|
||||||
|
|
||||||
import HeaderNav from "../components/HeaderNav"
|
import HeaderNav from "../components/HeaderNav"
|
||||||
import BottomNavigation from "../components/BottomNavigation"
|
import BottomNavigation from "../components/BottomNavigation"
|
||||||
@ -9,214 +10,100 @@ import video from "../assets/img/video.mov"
|
|||||||
|
|
||||||
import { connect } from '../confconnect';
|
import { connect } from '../confconnect';
|
||||||
import { getRouteExercise } from "../shared/consts/router"
|
import { getRouteExercise } from "../shared/consts/router"
|
||||||
|
import type { Exercise } from "./Exercise"
|
||||||
|
|
||||||
export const CourseExercises = () => {
|
|
||||||
|
interface RouteParams {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CourseExercises: React.FC =() => {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
// const { id } = useParams<{ id: string }>();
|
const { id } = useParams<RouteParams>(); // получаем ID курса из URL
|
||||||
const [currentSlide, setCurrentSlide] = useState(0)
|
const { title } = useParams<RouteParams>(); // получаем ID курса из URL
|
||||||
|
const [currentSlide, setCurrentSlide] = useState(0);
|
||||||
|
const token = localStorage.getItem('authToken');
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const course = {
|
console.log(token)
|
||||||
id: 2,
|
if (!token) {
|
||||||
description: 'Очень сильно',
|
setError('Токен не найден');
|
||||||
name: 'Качаем ноги',
|
return;
|
||||||
exercises: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: "Подъемы ног лежа",
|
|
||||||
duration: "15 мин",
|
|
||||||
sets: 3,
|
|
||||||
reps: 12,
|
|
||||||
image: "/placeholder.svg?height=200&width=300",
|
|
||||||
difficulty: "Легкий",
|
|
||||||
description: "Укрепление мышц бедра и улучшение подвижности коленного сустава",
|
|
||||||
calories: 45,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: "Приседания у стены",
|
|
||||||
duration: "10 мин",
|
|
||||||
sets: 2,
|
|
||||||
reps: 15,
|
|
||||||
image: "/placeholder.svg?height=200&width=300",
|
|
||||||
difficulty: "Средний",
|
|
||||||
description: "Безопасные приседания для восстановления силы ног",
|
|
||||||
calories: 60,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: "Растяжка квадрицепса",
|
|
||||||
duration: "8 мин",
|
|
||||||
sets: 1,
|
|
||||||
reps: 30,
|
|
||||||
image: "/placeholder.svg?height=200&width=300",
|
|
||||||
difficulty: "Легкий",
|
|
||||||
description: "Улучшение гибкости и снятие напряжения",
|
|
||||||
calories: 25,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: "Укрепление икр",
|
|
||||||
duration: "12 мин",
|
|
||||||
sets: 3,
|
|
||||||
reps: 20,
|
|
||||||
image: "/placeholder.svg?height=200&width=300",
|
|
||||||
difficulty: "Средний",
|
|
||||||
description: "Развитие силы и выносливости икроножных мышц",
|
|
||||||
calories: 40,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
|
|
||||||
// Функции для переключения на следующее/предыдущее упражнение
|
|
||||||
const nextExercise = () => {
|
|
||||||
setCurrentSlide((prev) => (prev + 1) % course.exercises.length)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevExercise = () => {
|
connect.get(`/pacient/${id}`, {
|
||||||
setCurrentSlide((prev) => (prev - 1 + course.exercises.length) % course.exercises.length)
|
headers: {
|
||||||
}
|
Authorization: `Bearer ${token}`,
|
||||||
const currentExercise = course.exercises[currentSlide]
|
},
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
console.log('Response status:', response.status);
|
||||||
|
console.log('')
|
||||||
|
|
||||||
|
// Предполагаемая структура:
|
||||||
|
// response.data.courses — массив пользователей
|
||||||
|
const exercises = response.data.exercises || [];
|
||||||
|
|
||||||
|
// Собираем все курсы из всех пользователей
|
||||||
|
const allCourse: Exercises[] = [];
|
||||||
|
|
||||||
|
users.forEach(user => {
|
||||||
|
if (user.Courses && Array.isArray(user.Courses)) {
|
||||||
|
user.Courses.forEach(course => {
|
||||||
|
// Можно добавить проверку или преобразование
|
||||||
|
allCourses.push({
|
||||||
|
ID: course.id, // или course.ID, зависит от структуры
|
||||||
|
title: course.title,
|
||||||
|
desc: course.desc,
|
||||||
|
url_file_img: course.url_file_img,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setCourses(allCourses);
|
||||||
|
})
|
||||||
|
.catch(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}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// const getDifficultyColor = (difficulty: string) => {
|
|
||||||
// switch (difficulty) {
|
|
||||||
// case "Легкий":
|
|
||||||
// return "bg-gradient-to-r from-emerald-400 to-green-500"
|
|
||||||
// case "Средний":
|
|
||||||
// return "bg-gradient-to-r from-amber-400 to-orange-500"
|
|
||||||
// case "Сложный":
|
|
||||||
// return "bg-gradient-to-r from-red-400 to-pink-500"
|
|
||||||
// default:
|
|
||||||
// return "bg-gradient-to-r from-gray-400 to-gray-500"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="bg-gray-50 w-full h-full overflow-auto">
|
<div className="bg-gray-50 w-full h-full overflow-auto">
|
||||||
<div className="my-36 min-h-screen max-w-4xl mx-auto">
|
<div className="my-36 min-h-screen max-w-4xl mx-auto">
|
||||||
<HeaderNav item={course.name} text={course.description} />
|
<HeaderNav item={'Курс:'} text={'все упражнения курса'} />
|
||||||
|
<div>
|
||||||
|
<h1>Курс ID: {id}</h1>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{/* Exercise Slider */}
|
{/* тут можете использовать id для загрузки данных или другого */}
|
||||||
<div className="px-4 sm:px-6 mt-34 mb-10 ">
|
|
||||||
<div className="glass-morphism rounded-3xl border border-white/50 shadow-2xl overflow-hidden backdrop-blur-2xl relative">
|
|
||||||
{/* Exercise Image */}
|
|
||||||
<div className="relative">
|
|
||||||
<video
|
|
||||||
src={video}
|
|
||||||
className="w-full h-120 object-cover"
|
|
||||||
autoPlay
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
playsInline
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-black/10"></div>
|
|
||||||
|
|
||||||
{/* Difficulty Badge */}
|
|
||||||
<div className="absolute top-4 right-4">
|
|
||||||
{/* <div
|
|
||||||
className={`px-4 py-2 rounded-full text-xs font-bold text-white shadow-lg backdrop-blur-sm ${getDifficultyColor(currentExercise.difficulty)}`}
|
|
||||||
>
|
|
||||||
{currentExercise.difficulty}
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Navigation arrows */}
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={prevExercise}
|
|
||||||
className="z-50 absolute left-4 top-1/2 transform -translate-y-1/2 w-12 h-12 bg-black/20 backdrop-blur-xl rounded-full flex items-center justify-center text-white hover:bg-black/30 transition-all duration-300 shadow-lg border border-white/20"
|
|
||||||
>
|
|
||||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={nextExercise}
|
|
||||||
className="z-50 absolute right-4 top-1/2 transform -translate-y-1/2 w-12 h-12 bg-black/20 backdrop-blur-xl rounded-full flex items-center justify-center text-white hover:bg-black/30 transition-all duration-300 shadow-lg border border-white/20"
|
|
||||||
>
|
|
||||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Exercise Info */}
|
|
||||||
<div className="p-6">
|
|
||||||
<h3 className="text-xl font-semibold text-gray-600 mb-3">{currentExercise.name}</h3>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => history.push(getRouteExercise(currentExercise.id))}
|
|
||||||
className="w-full bg-gradient-to-r bg-orange-400 text-white font-semibold py-4 px-6 rounded-2xl hover:shadow-2xl transition-all duration-300 transform hover:scale-105 shadow-lg backdrop-blur-sm"
|
|
||||||
>
|
|
||||||
Начать упражнение
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Slide indicators */}
|
|
||||||
<div className="flex justify-center mt-6 space-x-2">
|
|
||||||
{course.exercises.map((_, index) => (
|
|
||||||
<button
|
|
||||||
key={index}
|
|
||||||
onClick={() => setCurrentSlide(index)}
|
|
||||||
className={`h-4 rounded-full transition-all duration-300 ${index === currentSlide
|
|
||||||
? "bg-gradient-to-r from-[#2BACBE] to-cyan-600 w-8 shadow-lg"
|
|
||||||
: "bg-gray-300 w-4 hover:bg-gray-400"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Exercise List */}
|
|
||||||
<div className="px-4 sm:px-6 mb-50">
|
|
||||||
<h2 className="text-xl sm:text-2xl font-semibold text-gray-800 mb-6">Все упражнения курса</h2>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{course.exercises.map((exercise, index) => (
|
|
||||||
<div
|
|
||||||
key={exercise.id}
|
|
||||||
onClick={() => history.push(getRouteExercise(currentExercise.id))}
|
|
||||||
className={`glass-morphism rounded-2xl p-4 sm:p-6 border border-white/50 shadow-lg cursor-pointer transition-all duration-300 hover:shadow-2xl transform hover:scale-[1.02] backdrop-blur-xl ${index === currentSlide ? "ring-2 ring-[#2BACBE] bg-cyan-50/20" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div className="relative">
|
|
||||||
<div className="w-14 h-14 sm:w-16 sm:h-16 border-2 border-cyan-500 rounded-2xl flex items-center justify-center text-cyan-500 font-semibold text-lg sm:text-xl shadow-xl">
|
|
||||||
{index + 1}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<h3 className="font-semibold text-gray-800 text-lg sm:text-xl truncate">{exercise.name}</h3>
|
|
||||||
<p className="text-gray-600 text-sm mb-2 line-clamp-2">{exercise.description}</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="text-[#2BACBE] transform transition-transform duration-300 hover:translate-x-1">
|
|
||||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BottomNavigation />
|
<BottomNavigation />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default CourseExercises;
|
288
src/pages/CourseExercises_ old.tsx
Normal file
288
src/pages/CourseExercises_ old.tsx
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { useHistory, useParams } from "react-router-dom"
|
||||||
|
|
||||||
|
import HeaderNav from "../components/HeaderNav"
|
||||||
|
import BottomNavigation from "../components/BottomNavigation"
|
||||||
|
import video from "../assets/img/video.mov"
|
||||||
|
|
||||||
|
import { connect } from '../confconnect';
|
||||||
|
import { getRouteExercise } from "../shared/consts/router"
|
||||||
|
|
||||||
|
interface CourseExercise {
|
||||||
|
ID: number;
|
||||||
|
title: string;
|
||||||
|
desc: string;
|
||||||
|
url_file_img: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const CourseExercises = () => {
|
||||||
|
const history = useHistory()
|
||||||
|
// const { id } = useParams<{ id: string }>();
|
||||||
|
const [currentSlide, setCurrentSlide] = useState(0);
|
||||||
|
const token = localStorage.getItem('authToken');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(token)
|
||||||
|
if (!token) {
|
||||||
|
setError('Токен не найден');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect.get('/pacient/', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
console.log('Response status:', response.status);
|
||||||
|
|
||||||
|
// Предполагаемая структура:
|
||||||
|
// response.data.courses — массив пользователей
|
||||||
|
const users = response.data.courses || [];
|
||||||
|
|
||||||
|
// Собираем все курсы из всех пользователей
|
||||||
|
const allCourses: Course[] = [];
|
||||||
|
|
||||||
|
users.forEach(user => {
|
||||||
|
if (user.Courses && Array.isArray(user.Courses)) {
|
||||||
|
user.Courses.forEach(course => {
|
||||||
|
// Можно добавить проверку или преобразование
|
||||||
|
allCourses.push({
|
||||||
|
ID: course.id, // или course.ID, зависит от структуры
|
||||||
|
title: course.title,
|
||||||
|
desc: course.desc,
|
||||||
|
url_file_img: course.url_file_img,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setCourses(allCourses);
|
||||||
|
})
|
||||||
|
.catch(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}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
|
||||||
|
const course = {
|
||||||
|
id: 2,
|
||||||
|
description: 'Очень сильно',
|
||||||
|
name: 'Качаем ноги',
|
||||||
|
exercises: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Подъемы ног лежа",
|
||||||
|
duration: "15 мин",
|
||||||
|
sets: 3,
|
||||||
|
reps: 12,
|
||||||
|
image: "/placeholder.svg?height=200&width=300",
|
||||||
|
difficulty: "Легкий",
|
||||||
|
description: "Укрепление мышц бедра и улучшение подвижности коленного сустава",
|
||||||
|
calories: 45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Приседания у стены",
|
||||||
|
duration: "10 мин",
|
||||||
|
sets: 2,
|
||||||
|
reps: 15,
|
||||||
|
image: "/placeholder.svg?height=200&width=300",
|
||||||
|
difficulty: "Средний",
|
||||||
|
description: "Безопасные приседания для восстановления силы ног",
|
||||||
|
calories: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Растяжка квадрицепса",
|
||||||
|
duration: "8 мин",
|
||||||
|
sets: 1,
|
||||||
|
reps: 30,
|
||||||
|
image: "/placeholder.svg?height=200&width=300",
|
||||||
|
difficulty: "Легкий",
|
||||||
|
description: "Улучшение гибкости и снятие напряжения",
|
||||||
|
calories: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Укрепление икр",
|
||||||
|
duration: "12 мин",
|
||||||
|
sets: 3,
|
||||||
|
reps: 20,
|
||||||
|
image: "/placeholder.svg?height=200&width=300",
|
||||||
|
difficulty: "Средний",
|
||||||
|
description: "Развитие силы и выносливости икроножных мышц",
|
||||||
|
calories: 40,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
|
||||||
|
// Функции для переключения на следующее/предыдущее упражнение
|
||||||
|
const nextExercise = () => {
|
||||||
|
setCurrentSlide((prev) => (prev + 1) % course.exercises.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevExercise = () => {
|
||||||
|
setCurrentSlide((prev) => (prev - 1 + course.exercises.length) % course.exercises.length)
|
||||||
|
}
|
||||||
|
const currentExercise = course.exercises[currentSlide]
|
||||||
|
|
||||||
|
// const getDifficultyColor = (difficulty: string) => {
|
||||||
|
// switch (difficulty) {
|
||||||
|
// case "Легкий":
|
||||||
|
// return "bg-gradient-to-r from-emerald-400 to-green-500"
|
||||||
|
// case "Средний":
|
||||||
|
// return "bg-gradient-to-r from-amber-400 to-orange-500"
|
||||||
|
// case "Сложный":
|
||||||
|
// return "bg-gradient-to-r from-red-400 to-pink-500"
|
||||||
|
// default:
|
||||||
|
// return "bg-gradient-to-r from-gray-400 to-gray-500"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div className="bg-gray-50 w-full h-full overflow-auto">
|
||||||
|
<div className="my-36 min-h-screen max-w-4xl mx-auto">
|
||||||
|
<HeaderNav item={course.name} text={course.description} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* Exercise Slider */}
|
||||||
|
<div className="px-4 sm:px-6 mt-34 mb-10 ">
|
||||||
|
<div className="glass-morphism rounded-3xl border border-white/50 shadow-2xl overflow-hidden backdrop-blur-2xl relative">
|
||||||
|
{/* Exercise Image */}
|
||||||
|
<div className="relative">
|
||||||
|
<video
|
||||||
|
src={video}
|
||||||
|
className="w-full h-120 object-cover"
|
||||||
|
autoPlay
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-black/10"></div>
|
||||||
|
|
||||||
|
{/* Difficulty Badge */}
|
||||||
|
<div className="absolute top-4 right-4">
|
||||||
|
{/* <div
|
||||||
|
className={`px-4 py-2 rounded-full text-xs font-bold text-white shadow-lg backdrop-blur-sm ${getDifficultyColor(currentExercise.difficulty)}`}
|
||||||
|
>
|
||||||
|
{currentExercise.difficulty}
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navigation arrows */}
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={prevExercise}
|
||||||
|
className="z-50 absolute left-4 top-1/2 transform -translate-y-1/2 w-12 h-12 bg-black/20 backdrop-blur-xl rounded-full flex items-center justify-center text-white hover:bg-black/30 transition-all duration-300 shadow-lg border border-white/20"
|
||||||
|
>
|
||||||
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={nextExercise}
|
||||||
|
className="z-50 absolute right-4 top-1/2 transform -translate-y-1/2 w-12 h-12 bg-black/20 backdrop-blur-xl rounded-full flex items-center justify-center text-white hover:bg-black/30 transition-all duration-300 shadow-lg border border-white/20"
|
||||||
|
>
|
||||||
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Exercise Info */}
|
||||||
|
<div className="p-6">
|
||||||
|
<h3 className="text-xl font-semibold text-gray-600 mb-3">{currentExercise.name}</h3>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => history.push(getRouteExercise(currentExercise.id))}
|
||||||
|
className="w-full bg-gradient-to-r bg-orange-400 text-white font-semibold py-4 px-6 rounded-2xl hover:shadow-2xl transition-all duration-300 transform hover:scale-105 shadow-lg backdrop-blur-sm"
|
||||||
|
>
|
||||||
|
Начать упражнение
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Slide indicators */}
|
||||||
|
<div className="flex justify-center mt-6 space-x-2">
|
||||||
|
{course.exercises.map((_, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => setCurrentSlide(index)}
|
||||||
|
className={`h-4 rounded-full transition-all duration-300 ${index === currentSlide
|
||||||
|
? "bg-gradient-to-r from-[#2BACBE] to-cyan-600 w-8 shadow-lg"
|
||||||
|
: "bg-gray-300 w-4 hover:bg-gray-400"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* Exercise List */}
|
||||||
|
<div className="px-4 sm:px-6 mb-50">
|
||||||
|
<h2 className="text-xl sm:text-2xl font-semibold text-gray-800 mb-6">Все упражнения курса</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{course.exercises.map((exercise, index) => (
|
||||||
|
<div
|
||||||
|
key={exercise.id}
|
||||||
|
onClick={() => history.push(getRouteExercise(currentExercise.id))}
|
||||||
|
className={`glass-morphism rounded-2xl p-4 sm:p-6 border border-white/50 shadow-lg cursor-pointer transition-all duration-300 hover:shadow-2xl transform hover:scale-[1.02] backdrop-blur-xl ${index === currentSlide ? "ring-2 ring-[#2BACBE] bg-cyan-50/20" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-14 h-14 sm:w-16 sm:h-16 border-2 border-cyan-500 rounded-2xl flex items-center justify-center text-cyan-500 font-semibold text-lg sm:text-xl shadow-xl">
|
||||||
|
{index + 1}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h3 className="font-semibold text-gray-800 text-lg sm:text-xl truncate">{exercise.name}</h3>
|
||||||
|
<p className="text-gray-600 text-sm mb-2 line-clamp-2">{exercise.description}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="text-[#2BACBE] transform transition-transform duration-300 hover:translate-x-1">
|
||||||
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BottomNavigation />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -98,6 +98,9 @@ export const Courses = () => {
|
|||||||
});
|
});
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Генерируем случайный прогресс для каждого курса
|
// Генерируем случайный прогресс для каждого курса
|
||||||
const getRandomProgress = () => Math.floor(Math.random() * 100);
|
const getRandomProgress = () => Math.floor(Math.random() * 100);
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ export const getRouteHome = () => `/home`;
|
|||||||
export const getRouteForgotPassword = () => `/forgot-password`;
|
export const getRouteForgotPassword = () => `/forgot-password`;
|
||||||
|
|
||||||
export const getRouteCourses = () => `/courses`;
|
export const getRouteCourses = () => `/courses`;
|
||||||
export const getRouteCourseExercises = (id: number | string) => `/course/${id}/exercises`;
|
export const getRouteCourseExercises = (id: number | string) => `/course/${id}`;
|
||||||
export const getRouteExercise = (id: number | string) => `/exercise/${id}`;
|
export const getRouteExercise = (id: number | string) => `/exercise/${id}`;
|
||||||
|
|
||||||
export const getRouteSettings = () => `/settings`;
|
export const getRouteSettings = () => `/settings`;
|
||||||
|
@ -70,6 +70,17 @@ curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8093/pacient/courses
|
|||||||
"sessionname": "krasnova@mail.ru"
|
"sessionname": "krasnova@mail.ru"
|
||||||
|
|
||||||
|
|
||||||
curl -X GET \
|
curl -L -X GET \
|
||||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTYxMTEyNDQsInVzZXJfZW1haWwiOiJrcmFzbm92YUBtYWlsLnJ1IiwidXNlcl9pZCI6MjUsInVzZXJfbmFtZSI6ImtyYXNub3ZhQG1haWwucnUiLCJ1c2VyX3JvbGUiOiIifQ.3UyamwJrLVkdjKgUG16lYyKm1jiZAv6MRQW0Tj2Z3Tc" \
|
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTYxMTUzMTAsInVzZXJfZW1haWwiOiJrcmFzbm92YUBtYWlsLnJ1IiwidXNlcl9pZCI6MjUsInVzZXJfbmFtZSI6ImtyYXNub3ZhQG1haWwucnUiLCJ1c2VyX3JvbGUiOiIifQ.fkP4uDp9TylgLcVZlb5Zs7Po48DdBk42E0Xf1sHsMwk" \
|
||||||
http://localhost:8093/pacient/courses/:course_id
|
http://localhost:8093/pacient/courses/
|
||||||
|
|
||||||
|
ПОИСК ВНУТРИ КУРСА
|
||||||
|
|
||||||
|
curl -L -X GET \\
|
||||||
|
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTYxMTUzMTAsInVzZXJfZW1haWwiOiJrcmFzbm92YUBtYWlsLnJ1IiwidXNlcl9pZCI6MjUsInVzZXJfbmFtZSI6ImtyYXNub3ZhQG1haWwucnUiLCJ1c2VyX3JvbGUiOiIifQ.fkP4uDp9TylgLcVZlb5Zs7Po48DdBk42E0Xf1sHsMwk" \
|
||||||
|
http://localhost:8093/pacient/1
|
||||||
|
|
||||||
|
|
||||||
|
curl -L -X GET \
|
||||||
|
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||||
|
http://localhost:8093/pacient/course/1/
|
Loading…
x
Reference in New Issue
Block a user