228 lines
8.4 KiB
TypeScript
228 lines
8.4 KiB
TypeScript
"use client"
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import { useHistory } from "react-router-dom";
|
||
|
||
import BottomNavigation from "../components/BottomNavigation";
|
||
|
||
import HeaderNav from "../components/HeaderNav";
|
||
import { connect } from '../confconnect';
|
||
import { getRouteCourseExercises } from '../shared/consts/router';
|
||
|
||
import type { CourseExercises } from './CourseExercises';
|
||
|
||
export interface Course {
|
||
ID: number;
|
||
title: string;
|
||
desc: string;
|
||
url_file_img: string;
|
||
course_exercises: CourseExercises;
|
||
}
|
||
|
||
|
||
|
||
const ProgressLine = () => {
|
||
return (
|
||
<div
|
||
className="h-full bg-gradient-to-r from-orange-300 to-orange-500 transition-all duration-500 absolute left-0 top-0 rounded-l-xl"
|
||
style={{ width: '85%' }}
|
||
/>
|
||
);
|
||
}
|
||
|
||
const AnalitcsCards = () => {
|
||
return (
|
||
<div className="px-6 mb-8" >
|
||
<div className="text-center relative ">
|
||
<div className="w-full h-10 bg-gray-500 rounded-xl relative flex items-center justify-center mb-4 shadow-xl">
|
||
{/* Прогрессная часть */}
|
||
<ProgressLine />
|
||
|
||
{/* Текст поверх линии */}
|
||
<div className="absolute text-white font-semibold text-sm sm:text-base">Вы прошли реабилитацию на {85}%</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
|
||
|
||
// НАЧАЛО КОМПОНЕНТА
|
||
|
||
export const Courses = () => {
|
||
const history = useHistory();
|
||
|
||
const [courses, setCourses] = useState<Course[]>([]);
|
||
const [error, setError] = useState<string>('');
|
||
const token = localStorage.getItem('authToken');
|
||
|
||
const [course_exercises, setExercises] = useState<CourseExercises[]>([]);
|
||
|
||
useEffect(() => {
|
||
console.log(token)
|
||
if (!token) {
|
||
setError('Токен не найден');
|
||
return;
|
||
}
|
||
|
||
connect.get('/pacient/courses', {
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
},
|
||
})
|
||
.then(response => {
|
||
console.log('Response status:', response.data);
|
||
setExercises(response.data.courses.course_exercises);
|
||
|
||
// Предполагаемая структура:
|
||
// 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 getRandomProgress = () => Math.floor(Math.random() * 100);
|
||
|
||
// Цвета для прогресс-баров в оттенках cyan
|
||
const progressColors = [
|
||
"from-gray-400 to-cyan-800",
|
||
];
|
||
|
||
//item.exercise.title
|
||
|
||
|
||
return (
|
||
<div className="my-36 min-h-screen max-w-4xl mx-auto">
|
||
<HeaderNav item='Курсы' text='все назначенные' />
|
||
<AnalitcsCards />
|
||
|
||
|
||
<div className="px-6 mb-8">
|
||
{error && (
|
||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
|
||
<p className="text-red-600 font-medium">{error}</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Заголовок секции */}
|
||
<div className="flex flex-col sm:flex-row content-center justify-between mb-6">
|
||
<h2 className="text-xl font-black text-[#5F5C5C]">Мои курсы</h2>
|
||
<span className="text-sm text-gray-500"> Назначенных: {courses.length}</span>
|
||
</div>
|
||
|
||
{/* Выводим список курсов */}
|
||
<div className='space-y-4'>
|
||
{courses.length > 0 ? (
|
||
courses.map((course, index) => {
|
||
const progress = getRandomProgress();
|
||
const colorClass = progressColors[index % progressColors.length];
|
||
|
||
return (
|
||
<div
|
||
key={course.ID}
|
||
onClick={() => history.push(getRouteCourseExercises(course.ID.toString()), { course })}
|
||
className="bg-white/30 backdrop-blur-2xl rounded-3xl p-6 border border-white/20 shadow-xl cursor-pointer hover:shadow-2xl transition-all duration-300 transform hover:scale-[1.02]"
|
||
>
|
||
<div className="flex flex-col sm:flex-row sm:content-center space-x-5">
|
||
{/* Изображение курса */}
|
||
{course.url_file_img && (
|
||
<div className="h-10 w-10 md:h-20 md:w-20 rounded-2xl overflow-hidden flex-shrink-0">
|
||
<img
|
||
src={course.url_file_img || "/placeholder.svg"}
|
||
alt={course.title}
|
||
className="w-full h-full object-cover"
|
||
onError={(e) => {
|
||
e.currentTarget.src = "/placeholder.svg?height=64&width=64&text=Course"
|
||
}}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
<div className="flex-1">
|
||
<h3 className="font-semibold text-[#5F5C5C] text-lg mb-2">{course.title}</h3>
|
||
|
||
{/* Описание курса */}
|
||
{course.desc && (
|
||
<p className="text-sm text-[#5F5C5C]/60 mb-3 line-clamp-2">{course.desc}</p>
|
||
)}
|
||
|
||
{/* Индикатор прогресса */}
|
||
<div className="bg-white/50 rounded-full h-3 mb-2 overflow-hidden">
|
||
<div
|
||
className={`bg-gradient-to-r ${colorClass} h-3 rounded-full transition-all duration-700 shadow-sm`}
|
||
style={{ width: `${progress}%` }}
|
||
/>
|
||
</div>
|
||
|
||
{/* Информация о прогрессе */}
|
||
<div className="flex flex-col md:flex-row md:justify-between content-center">
|
||
<p className="text-sm text-[#5F5C5C]/70 font-semibold">{progress}% завершено</p>
|
||
<p className="text-xs text-[#5F5C5C]/50">{course_exercises} упражнений</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Иконка стрелки */}
|
||
<div className="hidden sm:block text-[#2BACBE] transform transition-transform duration-300 hover:translate-x-1 my-auto">
|
||
<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>
|
||
)
|
||
})
|
||
) : (
|
||
!error && (
|
||
<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.push("/courses")}
|
||
className="bg-[#2BACBE] text-white px-4 py-2 rounded-lg hover:bg-[#2A9FB8] transition-colors"
|
||
>
|
||
Просмотреть доступные курсы
|
||
</button>
|
||
</div>
|
||
)
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<BottomNavigation />
|
||
</div>
|
||
);
|
||
}; |