исправлена нижняя навигация - переход на текущее упражнение/тренировка
This commit is contained in:
parent
4429713601
commit
90267942d7
@ -1,32 +1,267 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import type React from "react"
|
||||
import { useHistory, useLocation } from "react-router-dom"
|
||||
import { useState, useEffect } from "react"
|
||||
import { connect } from "../confconnect"
|
||||
|
||||
import { getRouteHome } from "../shared/consts/router";
|
||||
import { getRouteCourses } from "../shared/consts/router";
|
||||
import { getRouteExerciseByIndex } from "../shared/consts/router";
|
||||
import { getRouteSettings } from "../shared/consts/router";
|
||||
import { getRouteHome } from "../shared/consts/router"
|
||||
import { getRouteCourses } from "../shared/consts/router"
|
||||
import { getRouteExerciseByIndex } from "../shared/consts/router"
|
||||
import { getRouteSettings } from "../shared/consts/router"
|
||||
|
||||
import { HomeIcon } from "./icons/HomeIcon";
|
||||
import { CalendarIcon } from "./icons/CalendarIcon";
|
||||
import { DumbbellIcon } from "./icons/DumbbellIcon";
|
||||
import { SettingsIcon } from "./icons/SettingsIcon";
|
||||
import { HomeIcon } from "./icons/HomeIcon"
|
||||
import { CalendarIcon } from "./icons/CalendarIcon"
|
||||
import { DumbbellIcon } from "./icons/DumbbellIcon"
|
||||
import { SettingsIcon } from "./icons/SettingsIcon"
|
||||
|
||||
import type { Course, User, CoursesApiResponse } from "../types/course"
|
||||
|
||||
interface CourseExercises {
|
||||
id_exercise: number
|
||||
day: number
|
||||
position: number
|
||||
title: string
|
||||
desc: string
|
||||
time: string
|
||||
count: number
|
||||
repeats: number
|
||||
}
|
||||
|
||||
interface CurrentExercise {
|
||||
courseId: string
|
||||
exerciseId: string
|
||||
exerciseIndex: number
|
||||
day: number
|
||||
title: string
|
||||
position: number
|
||||
isCompleted: boolean
|
||||
}
|
||||
|
||||
const BottomNavigation: React.FC = () => {
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
const [currentExercise, setCurrentExercise] = useState<CurrentExercise | null>(null)
|
||||
const [courses, setCourses] = useState<Course[]>([])
|
||||
|
||||
const token = localStorage.getItem("authToken")
|
||||
|
||||
const getCurrentExercise = async (): Promise<CurrentExercise | null> => {
|
||||
if (!token || courses.length === 0) return null
|
||||
|
||||
try {
|
||||
let targetCourse: Course | null = null
|
||||
|
||||
console.log("[v0] BottomNav - Всего курсов:", courses.length)
|
||||
console.log(
|
||||
"[v0] BottomNav - Список курсов:",
|
||||
courses.map((c) => ({ id: c.id, title: c.title })),
|
||||
)
|
||||
|
||||
// Проходим по всем курсам и ищем незавершенный
|
||||
for (const course of courses) {
|
||||
console.log("[v0] BottomNav - Проверяем курс:", course.id, course.title)
|
||||
|
||||
// Загружаем упражнения для каждого курса
|
||||
const response = await connect.get(`/pacient/${course.id}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
|
||||
const exercises = response.data.course_exercises || []
|
||||
console.log("[v0] BottomNav - Упражнений в курсе", course.id, ":", exercises.length)
|
||||
|
||||
if (exercises.length === 0) continue // Пропускаем курсы без упражнений
|
||||
|
||||
// Проверяем, есть ли незавершенные упражнения в этом курсе
|
||||
let hasIncompleteExercises = false
|
||||
let completedCount = 0
|
||||
|
||||
for (const exercise of exercises) {
|
||||
const storageKey = `exerciseProgress_${course.id}_${exercise.id_exercise}_day_${exercise.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) {
|
||||
completedCount++
|
||||
} else {
|
||||
hasIncompleteExercises = true
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[v0] BottomNav - Курс",
|
||||
course.id,
|
||||
"- завершено упражнений:",
|
||||
completedCount,
|
||||
"из",
|
||||
exercises.length,
|
||||
)
|
||||
console.log("[v0] BottomNav - Курс", course.id, "- есть незавершенные:", hasIncompleteExercises)
|
||||
|
||||
// Если в курсе есть незавершенные упражнения, выбираем его
|
||||
if (hasIncompleteExercises) {
|
||||
targetCourse = course
|
||||
console.log("[v0] BottomNav - Выбран курс как незавершенный:", course.id, course.title)
|
||||
break // Берем первый найденный незавершенный курс
|
||||
}
|
||||
}
|
||||
|
||||
// Если все курсы завершены, берем последний курс
|
||||
if (!targetCourse && courses.length > 0) {
|
||||
targetCourse = courses[courses.length - 1]
|
||||
console.log("[v0] BottomNav - Все курсы завершены, берем последний:", targetCourse.id, targetCourse.title)
|
||||
}
|
||||
|
||||
if (!targetCourse) {
|
||||
console.log("[v0] BottomNav - Не найден подходящий курс")
|
||||
return null
|
||||
}
|
||||
|
||||
console.log("[v0] BottomNav - Итоговый выбранный курс:", targetCourse.id, targetCourse.title)
|
||||
|
||||
// Загружаем упражнения выбранного курса
|
||||
const response = await connect.get(`/pacient/${targetCourse.id}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
|
||||
const exercises = response.data.course_exercises || []
|
||||
|
||||
if (exercises.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Группируем упражнения по дням
|
||||
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_${targetCourse.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: targetCourse.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: targetCourse.id.toString(),
|
||||
exerciseId: lastExercise.id_exercise.toString(),
|
||||
exerciseIndex: exercises.length - 1,
|
||||
day: lastExercise.day,
|
||||
title: lastExercise.title,
|
||||
position: lastExercise.position,
|
||||
isCompleted: true,
|
||||
}
|
||||
}
|
||||
|
||||
return foundExercise
|
||||
} catch (error) {
|
||||
console.error("[v0] BottomNav - Ошибка при получении текущего упражнения:", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return
|
||||
|
||||
connect
|
||||
.get<CoursesApiResponse>("/pacient/courses")
|
||||
.then((response) => {
|
||||
const users = response.data.courses || []
|
||||
const allCourses: Course[] = []
|
||||
|
||||
users.forEach((user: User) => {
|
||||
if (user.Courses && Array.isArray(user.Courses)) {
|
||||
user.Courses.forEach((course) => {
|
||||
allCourses.push({
|
||||
id: course.id,
|
||||
title: course.title,
|
||||
desc: course.desc,
|
||||
url_file_img: course.url_file_img,
|
||||
course_exercises: course.course_exercises,
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
setCourses(allCourses)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[v0] BottomNav - Ошибка при загрузке курсов:", error)
|
||||
})
|
||||
}, [token])
|
||||
|
||||
useEffect(() => {
|
||||
if (courses.length > 0) {
|
||||
getCurrentExercise().then(setCurrentExercise)
|
||||
}
|
||||
}, [courses])
|
||||
|
||||
const getWorkoutPath = () => {
|
||||
if (currentExercise) {
|
||||
return getRouteExerciseByIndex(currentExercise.courseId, currentExercise.exerciseIndex, currentExercise.day)
|
||||
}
|
||||
return getRouteExerciseByIndex(1, 0, 1)
|
||||
}
|
||||
|
||||
const navItems = [
|
||||
{ path: getRouteHome(), icon: HomeIcon, label: "Домой" },
|
||||
{ path: getRouteCourses(), icon: CalendarIcon, label: "Курсы" },
|
||||
{ path: getRouteExerciseByIndex(1,1,1), icon: DumbbellIcon, label: "Тренировка" },
|
||||
{ path: getWorkoutPath(), icon: DumbbellIcon, label: "Тренировка" },
|
||||
{ path: getRouteSettings(), icon: SettingsIcon, label: "Меню" },
|
||||
]
|
||||
|
||||
const isActive = (path: string) => {
|
||||
// Проверка на совпадение или включение
|
||||
return location.pathname === path || location.pathname.startsWith(path)
|
||||
}
|
||||
|
||||
@ -42,13 +277,11 @@ const BottomNavigation: React.FC = () => {
|
||||
onClick={() => history.push(item.path)}
|
||||
className="relative flex flex-col items-center justify-center w-24 h-24 overflow-hidden group focus:outline-none focus-visible:ring-1 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-[#0D212C]"
|
||||
>
|
||||
{/* Active state background (glassmorphism rectangle) */}
|
||||
<div
|
||||
className={`absolute rounded-2xl shadow-md transition-all duration-300 ease-out bg-white/60 backdrop-blur-md border border-white/20 ${
|
||||
active ? "opacity-100 scale-100 w-20 h-20 top-2" : "opacity-0 scale-0 w-0 h-0 top-0"
|
||||
}`}
|
||||
/>
|
||||
{/* Icon and Label container */}
|
||||
<div
|
||||
className={`relative z-10 flex flex-col items-center justify-center transition-all duration-300 ease-out ${
|
||||
active ? "text-[#145058]" : "text-white/70 translate-y-0 group-hover:text-white"
|
||||
@ -60,13 +293,11 @@ const BottomNavigation: React.FC = () => {
|
||||
<span className="text-xs font-medium">{item.label}</span>
|
||||
<span className="sr-only">{item.label}</span>
|
||||
</div>
|
||||
{/* Bottom circle with glow */}
|
||||
<div
|
||||
className={`absolute w-4 h-4 rounded-full border-1 border-cyan-900 shadow-lg shadow-[#2BACBE]/50 animate-pulse transition-all duration-300 ease-out ${
|
||||
active ? "opacity-100 scale-100 translate-y-[40px]" : "opacity-0 scale-0"
|
||||
}`}
|
||||
>
|
||||
{/* Внутренний маленький круг */}
|
||||
<div className="w-3 h-3 bg-cyan-900 rounded-full mt-[1px] ml-[1px]" />
|
||||
</div>
|
||||
</button>
|
||||
|
Loading…
x
Reference in New Issue
Block a user