разобралась с функцией загрузки прогресса с сервера

This commit is contained in:
Tatyana 2025-09-04 12:08:54 +03:00
parent cf9803ef6f
commit 2b9c198e44
2 changed files with 1149 additions and 102 deletions

View File

@ -41,12 +41,28 @@ export const Exercise = () => {
// ========== ОСНОВНЫЕ СОСТОЯНИЯ КОМПОНЕНТА ========== // ========== ОСНОВНЫЕ СОСТОЯНИЯ КОМПОНЕНТА ==========
// Состояние для хранения данных упражнения (null = данные еще не загружены) // Состояние для хранения данных упражнения (null = данные еще не загружены)
// Мы говорим TypeScript, что переменная exercise может быть либо объектом типа Exercise, либо null. Это важно, потому что изначально у нас нет данных (например, мы их ещё не загрузили), и состояние пустое — null
// exercise — это переменная/ текущее значение состояния, которое может быть объектом Exercise или null.
// setExercise — функция, с помощью которой можно обновить exercise.
// useState<string>("") — это вызов функции useState с аргументом "" (пустая строка).
// Внутри угловых скобок <string> мы указываем тип состояния (TypeScript).
// В круглых скобках ("") — передаём начальное значение состояния.
const [exercise, setExercise] = useState<Exercise | null>(null) const [exercise, setExercise] = useState<Exercise | null>(null)
// useState<Exercise | null>(null) — это вызов функции useState с начальным значением null
// Состояние загрузки (true = идет загрузка, false = загрузка завершена) // Состояние загрузки (true = идет загрузка, false = загрузка завершена)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
// если TypeScript может сам вывести тип из переданного начального значения, то указывать тип явно не обязательно
// Состояние ошибки (пустая строка = нет ошибки, текст = описание ошибки) // Состояние ошибки (пустая строка = нет ошибки, текст = описание ошибки)
// С помощью ДЕСТРУКТУРИЗАЦИИ МАССИВА мы присваиваем первый элемент массива переменной error, а второй — функции setError
// Деструктуризация массива — это удобный синтаксис в JavaScript/TypeScript, который позволяет распаковать значения из массива в отдельные переменные
// Когда начальное значение — null или undefined, и TypeScript не может понять, какого типа будет состояние.
const [error, setError] = useState<string>("") const [error, setError] = useState<string>("")
// ========== СОСТОЯНИЯ ТАЙМЕРУ УПРАЖНЕНИЯ ========== // ========== СОСТОЯНИЯ ТАЙМЕРУ УПРАЖНЕНИЯ ==========
@ -98,6 +114,10 @@ export const Exercise = () => {
const loadProgress = async () => { const loadProgress = async () => {
const serverLoaded = await loadProgressFromServer() const serverLoaded = await loadProgressFromServer()
// async — говорит, что функция работает асинхронно и возвращает промис.
// await — заставляет функцию ждать завершения промиса, не блокируя при этом весь поток выполнения.
if (!serverLoaded) { if (!serverLoaded) {
// Fallback to localStorage if server loading failed // Fallback to localStorage if server loading failed
console.log("Пытаемся загрузить прогресс из localStorage как резервный вариант") console.log("Пытаемся загрузить прогресс из localStorage как резервный вариант")
@ -208,90 +228,98 @@ export const Exercise = () => {
setLoading(false) setLoading(false)
}) })
}, [courseId, exerciseId]) // Код выполняется при изменении ID курса или упражнения }, [courseId, exerciseId]) // Код выполняется при изменении ID курса или упражнения
// ========== ФУНКЦИЯ ЗАГРУЗКИ ПРОГРЕССА С СЕРВЕРА ========== // ========== ФУНКЦИЯ ЗАГРУЗКИ ПРОГРЕССА С СЕРВЕРА ==========
// Эта функция получает сохраненный прогресс упражнения с бэкенда // Эта функция получает сохраненный прогресс упражнения с бэкенда
const loadProgressFromServer = async (): Promise<boolean> => { const loadProgressFromServer = async () => {
try { try {
console.log("Загружаем сохраненный прогресс с сервера для упражнения:", exerciseId, "в курсе:", courseId) console.log(`Загружаем прогресс для курса ${courseId} и упражнения ${exerciseId}`);
const response = await connect.get(`pacient/${courseId}/${exerciseId}/progress?course_id=${courseId}`) const token = localStorage.getItem('authToken');
if (response.data && response.data.length > 0) { // Выполняем GET-запрос на сервер
console.log("Найден сохраненный прогресс на сервере для курса", courseId, ":", response.data) const response = await connect.get(`pacient/${courseId}/${exerciseId}`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
// Получаем все записи прогресса для этого упражнения В ЭТОМ КУРСЕ // Логируем ответ
const progressData = response.data console.log("Ответ сервера с прогрессом:", response.data);
const filteredProgress = progressData.filter((record: any) => { // Проверяем наличие user_progress
// Проверяем что запись относится к текущему курсу if (
return record.course_id === Number.parseInt(courseId) || record.CourseId === Number.parseInt(courseId) response.data &&
}) response.data.user_progress &&
Array.isArray(response.data.user_progress) &&
response.data.user_progress.length > 0
) {
const progressArray = response.data.user_progress;
console.log("Отфильтрованный прогресс для курса", courseId, ":", filteredProgress) // Фильтруем прогресс по текущему курсу
const filteredProgress = progressArray.filter((record: any) => {
return record.course_id === Number.parseInt(courseId);
});
// Находим завершенные подходы (status = 1) и незавершенные (status = 0) console.log("Отфильтрованный прогресс:", filteredProgress);
const completedSetsFromServer: number[] = []
let lastIncompleteSet = null
let lastSavedTime = "00:00"
// Обрабатываем каждую запись прогресса ТОЛЬКО ДЛЯ ТЕКУЩЕГО КУРСА const completedSetsFromServer: number[] = [];
filteredProgress.forEach((record: any) => { let lastIncompleteSet: any = null;
console.log( let lastSavedTime = "00:00";
`Обрабатываем запись для курса ${courseId}: подход ${record.set}, статус ${record.status}, время ${record.time_users}`,
)
if (record.status === 1) { // Обрабатываем каждый прогресс
// СТАТУС 1 = подход завершен filteredProgress.forEach((record: any) => {
completedSetsFromServer.push(record.set) console.log(`Подход ${record.set}, статус ${record.status}, время ${record.time_users}`);
console.log(`Подход ${record.set} завершен`)
} else if (record.status === 0) { if (record.status === 1) {
// СТАТУС 0 = подход в процессе или на паузе // Завершённый подход
lastIncompleteSet = record completedSetsFromServer.push(record.set);
lastSavedTime = record.time_users || "00:00" } else if (record.status === 0) {
console.log(`Подход ${record.set} в процессе, время: ${lastSavedTime}`) // Незавершённый подход
if (!lastIncompleteSet || record.set > lastIncompleteSet.set) {
lastIncompleteSet = record;
lastSavedTime = record.time_users || "00:00";
} }
})
// Восстанавливаем состояние на основе данных с сервера
if (completedSetsFromServer.length > 0) {
console.log("Восстанавливаем завершенные подходы:", completedSetsFromServer)
setCompletedSets(completedSetsFromServer)
} }
});
// Если есть незавершенный подход, восстанавливаем его состояние // Восстановление завершённых подходов
if (lastIncompleteSet) { if (completedSetsFromServer.length > 0) {
console.log("Восстанавливаем незавершенный подход:", lastIncompleteSet.set) setCompletedSets(completedSetsFromServer);
// Конвертируем время из формата MM:SS в секунды
const timeInSeconds = convertTimeToSeconds(lastSavedTime)
setCurrentSet(lastIncompleteSet.set) // Устанавливаем текущий подход
setCurrentTime(timeInSeconds) // Восстанавливаем время
setHasSavedProgress(true) // Помечаем что есть сохраненный прогресс
setIsCompleted(false) // Упражнение не завершено
console.log(`Восстановлено состояние: подход ${lastIncompleteSet.set}, время ${timeInSeconds} секунд`)
}
// Проверяем, завершены ли все подходы
if (completedSetsFromServer.length >= totalSets) {
console.log("ВСЕ ПОДХОДЫ ЗАВЕРШЕНЫ согласно серверу!")
setIsCompleted(true) // Помечаем упражнение как завершенное
setCurrentTime(totalTime) // Устанавливаем время на максимум
setHasSavedProgress(false) // Сбрасываем флаг сохранения
}
return true // Successfully loaded from server
} else {
console.log("Прогресс на сервере не найден, начинаем с чистого листа")
return false // No progress found on server
} }
} catch (error) {
console.error("Ошибка при загрузке прогресса с сервера:", error)
return false // Failed to load from server
}
}
// Восстановление незавершённого подхода
if (lastIncompleteSet) {
const timeInSeconds = convertTimeToSeconds(lastSavedTime);
setCurrentSet(lastIncompleteSet.set);
setCurrentTime(timeInSeconds);
setHasSavedProgress(true);
setIsCompleted(false);
console.log(`Восстановлен незавершённый подход ${lastIncompleteSet.set} с временем ${timeInSeconds} секунд`);
}
// Проверка, завершены ли все подходы
if (completedSetsFromServer.length >= totalSets) {
setIsCompleted(true);
setCurrentTime(totalTime);
setHasSavedProgress(false);
console.log("Все подходы завершены");
}
return true;
} else {
console.log("Прогресс не найден, начинаем с чистого листа");
return false;
}
} catch (error) {
console.error("Ошибка при загрузке прогресса с сервера:", error);
return false;
}
};
// ========== ФУНКЦИЯ КОНВЕРТАЦИИ ВРЕМЕНИ ИЗ MM:SS В СЕКУНДЫ ========== // ========== ФУНКЦИЯ КОНВЕРТАЦИИ ВРЕМЕНИ ИЗ MM:SS В СЕКУНДЫ ==========
// Преобразует время из формата "MM:SS" в секунды (например: "02:30" = 150 секунд) // Преобразует время из формата "MM:SS" в секунды (например: "02:30" = 150 секунд)
const convertTimeToSeconds = (timeString: string): number => { const convertTimeToSeconds = (timeString: string): number => {
@ -350,7 +378,7 @@ export const Exercise = () => {
console.log(`!!!!Текущее упражнение: ${currentExerciseNum}, следующее: ${nextExerciseId}`) console.log(`!!!!Текущее упражнение: ${currentExerciseNum}, следующее: ${nextExerciseId}`)
console.log({courseId}, {nextExerciseId}) console.log({ courseId }, { nextExerciseId })
// Переходим к следующему упражнению в том же курсе // Переходим к следующему упражнению в том же курсе
history.push(`${courseId}/${nextExerciseId}`) history.push(`${courseId}/${nextExerciseId}`)
} }
@ -718,11 +746,10 @@ export const Exercise = () => {
<button <button
onClick={() => !isCompleted && !isResting && setIsPlaying(!isPlaying)} onClick={() => !isCompleted && !isResting && setIsPlaying(!isPlaying)}
disabled={isCompleted || isResting} disabled={isCompleted || isResting}
className={`w-20 h-20 rounded-full flex items-center justify-center shadow-2xl transition-all duration-300 transform hover:scale-110 ${ className={`w-20 h-20 rounded-full flex items-center justify-center shadow-2xl transition-all duration-300 transform hover:scale-110 ${isPlaying
isPlaying ? "bg-white/20 backdrop-blur-xl border border-white/30"
? "bg-white/20 backdrop-blur-xl border border-white/30" : "bg-white/30 backdrop-blur-xl border border-white/50"
: "bg-white/30 backdrop-blur-xl border border-white/50" } ${isCompleted || isResting ? "opacity-50 cursor-not-allowed" : ""}`}
} ${isCompleted || isResting ? "opacity-50 cursor-not-allowed" : ""}`}
> >
{isPlaying ? <PauseIcon /> : <PlayIcon />} {isPlaying ? <PauseIcon /> : <PlayIcon />}
</button> </button>
@ -782,9 +809,8 @@ export const Exercise = () => {
<div key={setNumber} className="flex-1 text-center"> <div key={setNumber} className="flex-1 text-center">
{/* Полоска прогресса для подхода */} {/* Полоска прогресса для подхода */}
<div <div
className={`h-3 rounded-full transition-all duration-300 ${ className={`h-3 rounded-full transition-all duration-300 ${isSetCompleted ? "bg-cyan-500" : isCurrent ? "bg-cyan-500" : "bg-gray-200"
isSetCompleted ? "bg-cyan-500" : isCurrent ? "bg-cyan-500" : "bg-gray-200" }`}
}`}
/> />
{/* Номер подхода под полоской */} {/* Номер подхода под полоской */}
<div className="text-xs font-bold mt-1 text-gray-600">{setNumber}</div> <div className="text-xs font-bold mt-1 text-gray-600">{setNumber}</div>
@ -825,13 +851,12 @@ export const Exercise = () => {
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{/* Индикатор активности */} {/* Индикатор активности */}
<div <div
className={`w-2 h-2 rounded-full ${ className={`w-2 h-2 rounded-full ${isCompleted
isCompleted ? "bg-cyan-400"
? "bg-cyan-400" : isResting
: isResting ? `bg-cyan-400 ${!isRestPaused ? "animate-pulse" : ""}`
? `bg-cyan-400 ${!isRestPaused ? "animate-pulse" : ""}` : "bg-cyan-400 animate-pulse"
: "bg-cyan-400 animate-pulse" }`}
}`}
></div> ></div>
{/* Текст состояния */} {/* Текст состояния */}
<span className="text-sm font-bold text-gray-700"> <span className="text-sm font-bold text-gray-700">
@ -851,13 +876,12 @@ export const Exercise = () => {
{/* Полоска прогресса */} {/* Полоска прогресса */}
<div className="bg-gray-200 rounded-full h-2 mb-4 overflow-hidden"> <div className="bg-gray-200 rounded-full h-2 mb-4 overflow-hidden">
<div <div
className={`h-2 rounded-full transition-all duration-1000 shadow-sm ${ className={`h-2 rounded-full transition-all duration-1000 shadow-sm ${isCompleted
isCompleted ? "bg-gradient-to-r from-cyan-400 via-cyan-500 to-cyan-600"
: isResting
? "bg-gradient-to-r from-cyan-400 via-cyan-500 to-cyan-600" ? "bg-gradient-to-r from-cyan-400 via-cyan-500 to-cyan-600"
: isResting : "bg-gradient-to-r from-[#2BACBE] via-cyan-500 to-cyan-700"
? "bg-gradient-to-r from-cyan-400 via-cyan-500 to-cyan-600" }`}
: "bg-gradient-to-r from-[#2BACBE] via-cyan-500 to-cyan-700"
}`}
style={{ width: `${progress}%` }} style={{ width: `${progress}%` }}
></div> ></div>
</div> </div>
@ -889,11 +913,10 @@ export const Exercise = () => {
// Сохраняем состояние паузы // Сохраняем состояние паузы
handlePause(completedSets, currentSet, true, restTime, !isRestPaused) handlePause(completedSets, currentSet, true, restTime, !isRestPaused)
}} }}
className={`flex-1 font-bold py-3 px-4 rounded-xl transition-all duration-300 flex items-center justify-center space-x-2 ${ className={`flex-1 font-bold py-3 px-4 rounded-xl transition-all duration-300 flex items-center justify-center space-x-2 ${isRestPaused
isRestPaused ? "bg-yellow-500 hover:bg-yellow-600 text-white"
? "bg-yellow-500 hover:bg-yellow-600 text-white" : "bg-[#2BACBE] hover:bg-[#2099A8] text-white"
: "bg-[#2BACBE] hover:bg-[#2099A8] text-white" }`}
}`}
> >
{isRestPaused ? ( {isRestPaused ? (
<> <>
@ -937,11 +960,10 @@ export const Exercise = () => {
setIsPlaying(false) setIsPlaying(false)
} }
}} }}
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 cursor-pointer ${ 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 cursor-pointer ${isPlaying
isPlaying ? "bg-yellow-500 hover:bg-yellow-600 text-white shadow-lg"
? "bg-yellow-500 hover:bg-yellow-600 text-white shadow-lg" : "bg-[#2BACBE] hover:bg-[#2099A8] text-white shadow-lg"
: "bg-[#2BACBE] hover:bg-[#2099A8] text-white shadow-lg" }`}
}`}
> >
{isPlaying ? ( {isPlaying ? (
<> <>

1025
src/pages/ExerciseOld.tsx Normal file

File diff suppressed because it is too large Load Diff