From df8cc1596c5cca04057635cf9483695319564285 Mon Sep 17 00:00:00 2001 From: Tatyana Date: Mon, 8 Sep 2025 14:46:31 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D0=BD=D0=BE=D0=B5=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=20=D0=B2=20=D1=81=D0=B5=D0=BA=D1=83=D0=BD=D0=B4=D0=B0?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CircularProgressDisplay.tsx | 20 +- src/components/cards/StatCardHome.tsx | 26 +- src/components/cards/WorkoutCardHome.tsx | 39 +- src/index.css | 2 +- src/pages/Courses.tsx | 309 ++++++++------- src/pages/Exercise.tsx | 50 +-- src/pages/Home.tsx | 416 +++++++++++++++------ src/types/course.ts | 2 +- 8 files changed, 551 insertions(+), 313 deletions(-) diff --git a/src/components/CircularProgressDisplay.tsx b/src/components/CircularProgressDisplay.tsx index 8d43281..91fd112 100644 --- a/src/components/CircularProgressDisplay.tsx +++ b/src/components/CircularProgressDisplay.tsx @@ -6,22 +6,26 @@ interface CircularProgressDisplayProps { overallProgress: number totalCourses: number totalExercises: number + completedCourses?: number + completedExercises?: number } const CircularProgressDisplay: React.FC = ({ overallProgress, totalCourses, totalExercises, + completedCourses = 0, + completedExercises = 0, }) => { const radius = 40 const circumference = 2 * Math.PI * radius - // For the "Courses" ring (cyan) - const coursesProgress = (totalCourses / 5) * 100 // Assuming max 5 courses for visual representation + // For the "Courses" ring (orange) - shows completed courses progress + const coursesProgress = totalCourses > 0 ? (completedCourses / totalCourses) * 100 : 0 const coursesStrokeDashoffset = circumference - (coursesProgress / 100) * circumference - // For the "Exercises" ring (green) - const exercisesProgress = (totalExercises / 50) * 100 // Assuming max 50 exercises for visual representation + // For the "Exercises" ring (cyan) - shows completed exercises progress + const exercisesProgress = totalExercises > 0 ? (completedExercises / totalExercises) * 100 : 0 const exercisesStrokeDashoffset = circumference - (exercisesProgress / 100) * circumference return ( @@ -38,7 +42,6 @@ const CircularProgressDisplay: React.FC = ({ cy="50" /> - = ({ strokeLinecap="round" stroke="currentColor" fill="transparent" - r={radius} + r={radius - 10} // Slightly smaller radius for the inner ring + cx="50" cy="50" transform="rotate(-90 50 50)" /> - = ({ strokeLinecap="round" stroke="currentColor" fill="transparent" - r={radius - 10} // Slightly smaller radius for the inner ring + r={radius} cx="50" cy="50" transform="rotate(-90 50 50)" @@ -70,7 +73,6 @@ const CircularProgressDisplay: React.FC = ({
{overallProgress}%
-
) diff --git a/src/components/cards/StatCardHome.tsx b/src/components/cards/StatCardHome.tsx index 39dd073..25b0474 100644 --- a/src/components/cards/StatCardHome.tsx +++ b/src/components/cards/StatCardHome.tsx @@ -4,14 +4,13 @@ // Если он взаимодействует с браузерными API или DOM. // Если он содержит обработчики событий или управляет состоянием. - import type React from "react" interface StatCardProps { title: string subtitle: string icon: React.ComponentType<{ size?: number; fill?: string; className?: string; style?: React.CSSProperties }> - fill:string + fill: string onClick: () => void } @@ -26,33 +25,18 @@ interface StatCardProps { // className?: string — необязственный CSS-класс. // style?: React.CSSProperties — необязательные inline-стили. - - - -export const StatCardHome: React.FC = ({ - title, - subtitle, - icon: Icon, - fill, - onClick, -}) => { +export const StatCardHome: React.FC = ({ title, subtitle, icon: Icon, fill, onClick }) => { return (
{/* Фоновая иконка */} - + {/* Основная иконка */}
- +
{/* Текст */} @@ -60,4 +44,4 @@ export const StatCardHome: React.FC = ({
{subtitle}
) -} \ No newline at end of file +} diff --git a/src/components/cards/WorkoutCardHome.tsx b/src/components/cards/WorkoutCardHome.tsx index 5511726..a41e703 100644 --- a/src/components/cards/WorkoutCardHome.tsx +++ b/src/components/cards/WorkoutCardHome.tsx @@ -2,14 +2,39 @@ import type React from "react" +interface CurrentExercise { + courseId: string + exerciseId: string + exerciseIndex: number + day: number + title: string + position: number + isCompleted: boolean +} + interface WorkoutCardProps { onBackClick: () => void onCardClick: () => void + currentExercise?: CurrentExercise | null + loading?: boolean } -// Это означает, что компонент ожидает получить пропс onBackClick, который должен быть функцией, вызываемой при определенном событии (например, при нажатии кнопки "Назад"). +export const WorkoutCardHome: React.FC = ({ onBackClick, onCardClick, currentExercise, loading }) => { + const getExerciseStatus = () => { + if (loading) return "Загрузка..." + if (!currentExercise) return "Нет активных упражнений" + if (currentExercise.isCompleted) return "Все упражнения завершены" + return "В процессе" + } + + const getExerciseDescription = () => { + if (loading) return "Поиск текущего упражнения..." + if (!currentExercise) return "Начните новый курс" + if (currentExercise.isCompleted) return "Отличная работа!" + return `Курс ${currentExercise.courseId} / День ${currentExercise.day}` + + } -export const WorkoutCardHome: React.FC = ({ onBackClick, onCardClick }) => { return (
= ({ onBackClick, onCar
{/* Пульсирующая точка */} -
+
@@ -60,8 +89,8 @@ export const WorkoutCardHome: React.FC = ({ onBackClick, onCar {/* Информация о упражнении */}
-

В процессе

-

Текущее упражнение

+

{getExerciseStatus()}

+

{getExerciseDescription()}

diff --git a/src/index.css b/src/index.css index 52c4563..4c7691a 100644 --- a/src/index.css +++ b/src/index.css @@ -17,7 +17,7 @@ @font-face { font-family: 'Poppins'; - src: url('../src/assets/fonts/Poppins-Semibold.woff2') format('woff2'); + src: url('../src/assets/fonts/Poppins-SemiBold.woff2') format('woff2'); font-weight: 600; font-style: normal; } diff --git a/src/pages/Courses.tsx b/src/pages/Courses.tsx index 89f5d2b..2df301d 100644 --- a/src/pages/Courses.tsx +++ b/src/pages/Courses.tsx @@ -1,100 +1,139 @@ "use client" -import { useState, useEffect } from 'react'; -import { useHistory } from "react-router-dom"; +import { useState, useEffect } from "react" +import { useHistory } from "react-router-dom" -import BottomNavigation from "../components/BottomNavigation"; +import BottomNavigation from "../components/BottomNavigation" -import HeaderNav from "../components/HeaderNav"; -import { connect } from '../confconnect'; -import { getRouteCourseExercises } from '../shared/consts/router'; +import HeaderNav from "../components/HeaderNav" +import { connect } from "../confconnect" +import { getRouteCourseExercises } from "../shared/consts/router" -import type { CourseExercises } from './CourseExercises'; +import type { CourseExercises } from "./CourseExercises" export interface Course { - ID: number; - title: string; - desc: string; - url_file_img: string; - course_exercises: CourseExercises; + ID: number + title: string + desc: string + url_file_img: string + course_exercises: CourseExercises } interface ResponseData { - courses: User[]; - course_exercises: CourseExercises; + courses: User[] + course_exercises: CourseExercises } interface User { - id: number; - name: string; - Courses?: Course[]; + id: number + name: string + Courses?: Course[] } - -const ProgressLine = () => { +const ProgressLine = ({ progress }: { progress: number }) => { return (
- ); + ) } -const AnalitcsCards = () => { +const AnalitcsCards = ({ overallProgress }: { overallProgress: number }) => { return ( -
+
{/* Прогрессная часть */} - + {/* Текст поверх линии */} -
Вы прошли реабилитацию на {85}%
+
+ Вы прошли реабилитацию на {overallProgress}% +
) } +const calculateCourseProgress = async (course: Course, token: string): Promise => { + try { + const response = await connect.get(`/pacient/${course.ID}`, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }) + const exercises = response.data.course_exercises || [] + if (exercises.length === 0) return 0 + + let completedExercises = 0 + + for (const exercise of exercises) { + const storageKey = `exerciseProgress_${course.ID}_${exercise.id_exercise}_day_${exercise.day}` + const savedProgress = localStorage.getItem(storageKey) + + if (savedProgress) { + const progress = JSON.parse(savedProgress) + const isCompleted = + progress.status === 1 && progress.completedSets && progress.completedSets.length >= exercise.count + + if (isCompleted) { + completedExercises++ + } + } + } + + return Math.round((completedExercises / exercises.length) * 100) + } catch (error) { + console.error(`Ошибка при расчете прогресса курса ${course.ID}:`, error) + return 0 + } +} // НАЧАЛО КОМПОНЕНТА export const Courses = () => { - const history = useHistory(); + const history = useHistory() - const [courses, setCourses] = useState([]); - const [error, setError] = useState(''); - const token = localStorage.getItem('authToken'); + const [courses, setCourses] = useState([]) + const [error, setError] = useState("") + const token = localStorage.getItem("authToken") - const [, setExercises] = useState([]); + const [, setExercises] = useState([]) + + const [courseProgress, setCourseProgress] = useState<{ [key: number]: number }>({}) + const [overallProgress, setOverallProgress] = useState(0) useEffect(() => { console.log(token) if (!token) { - setError('Токен не найден'); - return; + setError("Токен не найден") + return } - connect.get('/pacient/courses', { - headers: { - Authorization: `Bearer ${token}`, - }, - }) + connect + .get("/pacient/courses", { + headers: { + Authorization: `Bearer ${token}`, + }, + }) .then((response: { data: ResponseData }) => { - console.log('Response status:', response.data); - setExercises(response.data.courses.course_exercises); + console.log("Response status:", response.data) + setExercises(response.data.courses.course_exercises) // Предполагаемая структура: // response.data.courses — массив пользователей - const users = response.data.courses || []; + const users = response.data.courses || [] // Собираем все курсы из всех пользователей - const allCourses: Course[] = []; + const allCourses: Course[] = [] - users.forEach(user => { + users.forEach((user) => { if (user.Courses && Array.isArray(user.Courses)) { - user.Courses.forEach(course => { + user.Courses.forEach((course) => { // Можно добавить проверку или преобразование allCourses.push({ ID: course.id, // или course.ID, зависит от структуры @@ -102,46 +141,56 @@ export const Courses = () => { desc: course.desc, url_file_img: course.url_file_img, course_exercises: course.course_exercises, - }); - }); + }) + }) } - }); + }) - - setCourses(allCourses); + setCourses(allCourses) }) - .catch(error => { + .catch((error) => { if (error.response) { - console.error('Ошибка ответа сервера:', error.response.status, error.response.data); - setError(`Ошибка сервера: ${error.response.status}`); + console.error("Ошибка ответа сервера:", error.response.status, error.response.data) + setError(`Ошибка сервера: ${error.response.status}`) } else if (error.request) { - console.error('Нет ответа от сервера:', error.request); - setError('Нет ответа от сервера'); + console.error("Нет ответа от сервера:", error.request) + setError("Нет ответа от сервера") } else { - console.error('Ошибка при настройке запроса:', error.message); - setError(`Ошибка: ${error.message}`); + console.error("Ошибка при настройке запроса:", error.message) + setError(`Ошибка: ${error.message}`) } - }); - }, [token]); + }) + }, [token]) + useEffect(() => { + if (courses.length > 0 && token) { + const calculateAllProgress = async () => { + const progressMap: { [key: number]: number } = {} + let totalProgress = 0 + for (const course of courses) { + const progress = await calculateCourseProgress(course, token) + progressMap[course.ID] = progress + totalProgress += progress + } - // Генерируем случайный прогресс для каждого курса - const getRandomProgress = () => Math.floor(Math.random() * 100); + setCourseProgress(progressMap) + setOverallProgress(Math.round(totalProgress / courses.length)) + } + + calculateAllProgress() + } + }, [courses, token]) // Цвета для прогресс-баров в оттенках cyan - const progressColors = [ - "from-cyan-600 to-cyan-900", - ]; + const progressColors = ["from-cyan-600 to-cyan-900"] //item.exercise.title - return (
- - - + +
{error && ( @@ -157,83 +206,79 @@ export const Courses = () => {
{/* Выводим список курсов */} -
- {courses.length > 0 ? ( - courses.map((course, index) => { - const progress = getRandomProgress(); - const colorClass = progressColors[index % progressColors.length]; +
+ {courses.length > 0 + ? courses.map((course, index) => { + const progress = courseProgress[course.ID] || 0 + const colorClass = progressColors[index % progressColors.length] - return ( -
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]" - > -
- {/* Изображение курса */} - {course.url_file_img && ( -
- {course.title} { - e.currentTarget.src = "/placeholder.svg?height=64&width=64&text=Course" - }} - /> -
- )} - -
-

{course.title}

- - {/* Описание курса */} - {course.desc && ( -

{course.desc}

+ return ( +
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]" + > +
+ {/* Изображение курса */} + {course.url_file_img && ( +
+ {course.title} { + e.currentTarget.src = "/placeholder.svg?height=64&width=64&text=Course" + }} + /> +
)} - {/* Индикатор прогресса */} -
-
+
+

{course.title}

+ + {/* Описание курса */} + {course.desc &&

{course.desc}

} + + {/* Индикатор прогресса */} +
+
+
+ + {/* Информация о прогрессе */} +
+

{progress}% завершено

+

{"надо/не надо?"} упражнений

+
- {/* Информация о прогрессе */} -
-

{progress}% завершено

-

{"надо/не надо?"} упражнений

+ {/* Иконка стрелки */} +
+ + +
- - {/* Иконка стрелки */} -
- - - -
+ ) + }) + : !error && ( +
+

У вас пока нет назначенных курсов

+
- ) - }) - ) : ( - !error && ( -
-

У вас пока нет назначенных курсов

- -
- ) - )} + )}
- ); -}; \ No newline at end of file + ) +} diff --git a/src/pages/Exercise.tsx b/src/pages/Exercise.tsx index 0c54a16..d641fce 100644 --- a/src/pages/Exercise.tsx +++ b/src/pages/Exercise.tsx @@ -41,7 +41,6 @@ export const Exercise = () => { const { courseId, exerciseId, exerciseIndex } = useParams() const location = useLocation() - const [course_exercises, setCourseExercises] = useState([]) const [actualExerciseId, setActualExerciseId] = useState("") const [currentDay, setCurrentDay] = useState(null) @@ -68,7 +67,7 @@ export const Exercise = () => { // ========== СОСТОЯНИЯ ОТДЫХА МЕЖДУ ПОДХОДАМИ ========== const [isResting, setIsResting] = useState(false) const [restTime, setRestTime] = useState(0) - const [totalRestTime] = useState(60) + const [totalRestTime] = useState(10) // Set default rest time to 60 seconds const [isRestPaused, setIsRestPaused] = useState(false) useEffect(() => { @@ -154,7 +153,14 @@ export const Exercise = () => { }) if (exerciseData.time) { - const timeInSeconds = Number.parseInt(exerciseData.time) * 60 + let timeInSeconds: number + if (typeof exerciseData.time === "string" && exerciseData.time.includes(":")) { + // Time is in MM:SS format, convert to seconds + timeInSeconds = convertTimeToSeconds(exerciseData.time) + } else { + // Time is already in seconds + timeInSeconds = Number.parseInt(exerciseData.time) + } setTotalTime(timeInSeconds) console.log("Установлено время упражнения:", timeInSeconds, "секунд") } @@ -198,7 +204,6 @@ export const Exercise = () => { }, }) - console.log("Ответ сервера с прогрессом:", response.data) if ( @@ -213,7 +218,7 @@ export const Exercise = () => { const matchesCourse = record.course_id === Number.parseInt(courseId) const matchesDay = currentDay ? record.day === currentDay : true console.log( - "[v0] Filtering progress - Course match:", + "Filtering progress - Course match:", matchesCourse, "Day match:", matchesDay, @@ -249,7 +254,12 @@ export const Exercise = () => { } if (lastIncompleteSet) { - const timeInSeconds = convertTimeToSeconds(lastSavedTime) + let timeInSeconds: number + if (typeof lastSavedTime === "string" && lastSavedTime.includes(":")) { + timeInSeconds = convertTimeToSeconds(lastSavedTime) + } else { + timeInSeconds = Number.parseInt(lastSavedTime) || 0 + } setCurrentSet(lastIncompleteSet.set) setCurrentTime(timeInSeconds) @@ -303,9 +313,9 @@ export const Exercise = () => { 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) + console.log(`[SAVING PROGRESS: Set ${setNumber}, Status ${status}, Day ${dayToSave}`) + console.log(`[Current state - currentDay: ${currentDay}, dayToSave: ${dayToSave}`) + console.log(`[URL search params:`, location.search) const progressData = { time_users: timeString, @@ -327,21 +337,13 @@ export const Exercise = () => { } } - - - console.log("TEST DAY COMPLETE", course_exercises) - console.log("Всего упражнений в курсе", course_exercises.length) - - - - - + 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 @@ -349,13 +351,11 @@ export const Exercise = () => { 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}`) - } + } } // ========== ФУНКЦИЯ ЗАВЕРШЕНИЯ ТЕКУЩЕГО ПОДХОДА ========== @@ -584,8 +584,9 @@ export const Exercise = () => { // ========== ФУНКЦИЯ ФОРМАТИРОВАНИЯ ВРЕМЕНИ ========== const formatTime = (seconds: number) => { - const mins = Math.floor(seconds / 60) - const secs = seconds % 60 + const totalSeconds = Math.round(seconds) + const mins = Math.floor(totalSeconds / 60) + const secs = totalSeconds % 60 return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}` } @@ -873,7 +874,6 @@ export const Exercise = () => { Следующее упражнение -
Завершено diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 4b618e7..9c8a78a 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,49 +1,242 @@ "use client" -import { useEffect, useState } from "react"; -import { useHistory } from "react-router-dom"; +import { useEffect, useState } from "react" +import { useHistory } from "react-router-dom" +// import { useParams } from "react-router-dom" -import { CalendarIcon } from "../components/icons/CalendarIcon"; -import { DumbbellIcon } from "../components/icons/DumbbellIcon"; +import { CalendarIcon } from "../components/icons/CalendarIcon" +import { DumbbellIcon } from "../components/icons/DumbbellIcon" +import HeaderNav from "../components/HeaderNav" +import BottomNavigation from "../components/BottomNavigation" +import CircularProgressDisplay from "../components/CircularProgressDisplay" -import HeaderNav from "../components/HeaderNav"; -import BottomNavigation from "../components/BottomNavigation"; -import CircularProgressDisplay from "../components/CircularProgressDisplay"; +import { StatCardHome } from "../components/cards/StatCardHome" +import { WorkoutCardHome } from "../components/cards/WorkoutCardHome" +import { connect } from "../confconnect" +import { getRouteExerciseByIndex } from "../shared/consts/router" +import { getRouteCourses } from "../shared/consts/router" +import { getRouteCourseExercises } from "../shared/consts/router" -import { StatCardHome } from "../components/cards/StatCardHome"; -import { WorkoutCardHome } from "../components/cards/WorkoutCardHome"; +import type { Course, User, CoursesApiResponse } from "../types/course" -import { connect } from '../confconnect'; -import { getRouteExerciseByIndex } from "../shared/consts/router"; -import { getRouteCourses } from "../shared/consts/router"; -import { getRouteCourseExercises } from "../shared/consts/router"; +interface CourseExercises { + id_exercise: number + day: number + position: number + title: string + desc: string + time: string + count: number + repeats: number +} -import type { Course, User, CoursesApiResponse } from "../types/course"; +interface CurrentExercise { + courseId: string + exerciseId: string + exerciseIndex: number + day: number + title: string + position: number + isCompleted: boolean +} + +interface ProgressStats { + completedExercises: number + totalExercises: number + completedCourses: number + totalCourses: number + overallProgress: number +} + +// interface RouteParams { +// courseId: string +// exerciseId?: string // Made optional since we might have exerciseIndex instead +// exerciseIndex?: string // Added exerciseIndex parameter +// } -//НАЧАЛО // export default function Home() { + const history = useHistory() + const [currentDate, setCurrentDate] = useState("") + const [error, setError] = useState("") + const [courses, setCourses] = useState([]) + const [loading, setLoading] = useState(true) - const history = useHistory(); - const [currentDate, setCurrentDate] = useState(""); - const [, setError] = useState(''); - const [courses, setCourses] = useState([]); - const [loading, setLoading] = useState(true); + const [currentExercise, setCurrentExercise] = useState(null) + const [loadingCurrentExercise, setLoadingCurrentExercise] = useState(false) + // const { courseId, exerciseId, exerciseIndex } = useParams() - const token = localStorage.getItem('authToken'); + const [progressStats, setProgressStats] = useState({ + completedExercises: 0, + totalExercises: 0, + completedCourses: 0, + totalCourses: 0, + overallProgress: 0, + }) - const exerciseProgress = localStorage.getItem('exerciseProgress'); - const currentProgress = localStorage.getItem('currentProgress') - + const token = localStorage.getItem("authToken") + + const calculateProgressStats = async (coursesList: Course[]): Promise => { + let totalExercises = 0 + let completedExercises = 0 + let completedCourses = 0 + + for (const course of coursesList) { + try { + // Получаем упражнения для каждого курса + const response = await connect.get(`/pacient/${course.id}`, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }) + + const exercises = response.data.course_exercises || [] + totalExercises += exercises.length + + console.log("упражнения для каждого курса", response.data) + + let courseCompletedExercises = 0 + + // Проверяем каждое упражнение на завершенность + for (const exercise of exercises) { + const storageKey = `exerciseProgress_${course.id}_${exercise.id_exercise}_day_${exercise.day}` + const savedProgress = localStorage.getItem(storageKey) + + if (savedProgress) { + const progress = JSON.parse(savedProgress) + const isCompleted = + progress.status === 1 && progress.completedSets && progress.completedSets.length >= exercise.count + + if (isCompleted) { + completedExercises++ + courseCompletedExercises++ + } + } + } + + // Курс считается завершенным если все упражнения выполнены + if (exercises.length > 0 && courseCompletedExercises === exercises.length) { + completedCourses++ + } + } catch (error) { + console.error(`Ошибка при получении упражнений для курса ${course.id}:`, error) + // Если не удалось загрузить упражнения, считаем что в курсе 0 упражнений + } + } + + const overallProgress = totalExercises > 0 ? Math.round((completedExercises / totalExercises) * 100) : 0 + + return { + completedExercises, + totalExercises, + completedCourses, + totalCourses: coursesList.length, + overallProgress, + } + } + + const getCurrentExercise = async () => { + if (courses.length === 0) return + + setLoadingCurrentExercise(true) + + try { + // Берем первый доступный курс + const firstCourse = courses[0] + + // Загружаем упражнения курса + const response = await connect.get(`/pacient/${firstCourse.id}`, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }) + + const exercises = response.data.course_exercises || [] + + if (exercises.length === 0) { + setCurrentExercise(null) + return + } + + // Группируем упражнения по дням + const exercisesByDay = exercises.reduce((acc: any, exercise: CourseExercises) => { + if (!acc[exercise.day]) { + acc[exercise.day] = [] + } + acc[exercise.day].push(exercise) + return acc + }, {}) + + // Ищем первое незавершенное упражнение + let foundExercise: CurrentExercise | null = null + + for (const day of Object.keys(exercisesByDay).sort((a, b) => Number(a) - Number(b))) { + const dayExercises = exercisesByDay[day].sort( + (a: CourseExercises, b: CourseExercises) => a.position - b.position, + ) + + for (let i = 0; i < dayExercises.length; i++) { + const exercise = dayExercises[i] + + // Проверяем прогресс упражнения в localStorage + const storageKey = `exerciseProgress_${firstCourse.id}_${exercise.id_exercise}_day_${day}` + const savedProgress = localStorage.getItem(storageKey) + + let isCompleted = false + if (savedProgress) { + const progress = JSON.parse(savedProgress) + isCompleted = + progress.status === 1 && progress.completedSets && progress.completedSets.length >= exercise.count + } + + if (!isCompleted) { + foundExercise = { + courseId: firstCourse.id.toString(), + exerciseId: exercise.id_exercise.toString(), + exerciseIndex: i, + day: Number(day), + title: exercise.title, + position: exercise.position, + isCompleted: false, + } + break + } + } + + if (foundExercise) break + } + + // Если все упражнения завершены, берем последнее + if (!foundExercise && exercises.length > 0) { + const lastExercise = exercises[exercises.length - 1] + foundExercise = { + courseId: firstCourse.id.toString(), + exerciseId: lastExercise.id_exercise.toString(), + exerciseIndex: exercises.length - 1, + day: lastExercise.day, + title: lastExercise.title, + position: lastExercise.position, + isCompleted: true, + } + } + + setCurrentExercise(foundExercise) + } catch (error) { + console.error("Ошибка при получении текущего упражнения:", error) + } finally { + setLoadingCurrentExercise(false) + } + } useEffect(() => { - console.log(token) if (!token) { - setError('Токен не найден'); - setLoading(false); - return; + setError("Токен не найден") + setLoading(false) + return } setCurrentDate( @@ -54,9 +247,9 @@ export default function Home() { }), ) - setLoading(true) + setLoading(true) connect - .get("/pacient/courses") + .get("/pacient/courses") .then((response) => { console.log("Response data:", response.data) @@ -67,7 +260,7 @@ export default function Home() { if (user.Courses && Array.isArray(user.Courses)) { user.Courses.forEach((course) => { allCourses.push({ - ID: course.ID, + id: course.id, title: course.title, desc: course.desc, url_file_img: course.url_file_img, @@ -97,21 +290,26 @@ export default function Home() { }) }, [token]) -// Расчет статистики / выполненные где брать??? - const totalCourses = courses.length - const totalExercises = courses.reduce((sum, course) => { - if (course.course_exercises && Array.isArray(course.course_exercises)) { - return sum + course.course_exercises.length + useEffect(() => { + if (courses.length > 0 && !loading) { + getCurrentExercise() + + calculateProgressStats(courses).then((stats) => { + setProgressStats(stats) + }) } - return sum + Math.floor(Math.random() * 10) + 5 - }, 0) - - const overallProgress = courses.length > 0 ? Math.floor(Math.random() * 100) : 0 - -//пока передаю тестовые значения + }, [courses, loading]) const handleWorkoutClick = () => { - history.push(getRouteExerciseByIndex(1,1,1)) + if (currentExercise) { + // Используем правильный роут с day параметром + history.push( + getRouteExerciseByIndex(currentExercise.courseId, currentExercise.exerciseIndex, currentExercise.day), + ) + } else { + // Fallback если нет текущего упражнения + history.push(getRouteExerciseByIndex(1, 0, 1)) + } } const handleBackClick = () => { @@ -124,7 +322,7 @@ export default function Home() { const handleExercisesClick = () => { if (courses.length > 0) { - history.push(getRouteCourseExercises(`${'1'}`)) + history.push(getRouteCourseExercises(courses[0].id)) } else { history.push(getRouteCourses()) } @@ -144,88 +342,68 @@ export default function Home() { ) } + return ( +
+
+ - return ( -
-
- - -
-
- - - - -
-
-
-
-
Все курсы
-
{totalCourses}/?
-
-
-
Все упражнения
-
{totalExercises}/?
+
+
+
+
+
+
Все курсы
+
+ {progressStats.completedCourses}/{progressStats.totalCourses}
-
- +
+
Все упражнения
+
+ {progressStats.completedExercises}/{progressStats.totalExercises} +
-
- -
- {/* Текущее упражнение */} - - - - {/* Quick Stats (Total Exercises & Total Courses) */} -
- - +
- -
-

Взяли из локального хранилища:

- надо считать прогресс исходя из выполненных/ вопрос: то есть, если выполнено, то как его исключить / как флаг проставлять?== - все курсы мы вяли из ручки курсов, назначенные по роутингу (пути в router.ts) - - надо достать текущий курс. который не выполнен. - - как узнать, что курс выполнен?== - - мы берем данные для текущей тренировки, которые записываются с помощью post, --оттуда достаем данные для текущего упражнения, которое записано (1 строка в БД всегда), - а потом переходим на это упражнение. - - и еще - мы считаем по индексу отображение, это же не повлияет на вывод итоговый на домашней странице, например? - - -
-

exerciseProgress:{exerciseProgress}

-

currentProgress{currentProgress}

-
-
- -
+ +
+ + +
+ + +
+
+ +
- ) - } \ No newline at end of file +
+ ) +} diff --git a/src/types/course.ts b/src/types/course.ts index 9f70d2b..1ce3913 100644 --- a/src/types/course.ts +++ b/src/types/course.ts @@ -8,7 +8,7 @@ export interface CourseExercise { } export interface Course { - ID: number + id: number title: string desc: string url_file_img: string