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

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[] = [];
let lastIncompleteSet: any = null;
let lastSavedTime = "00:00";
// Обрабатываем каждый прогресс
filteredProgress.forEach((record: any) => { filteredProgress.forEach((record: any) => {
console.log( console.log(`Подход ${record.set}, статус ${record.status}, время ${record.time_users}`);
`Обрабатываем запись для курса ${courseId}: подход ${record.set}, статус ${record.status}, время ${record.time_users}`,
)
if (record.status === 1) { if (record.status === 1) {
// СТАТУС 1 = подход завершен // Завершённый подход
completedSetsFromServer.push(record.set) completedSetsFromServer.push(record.set);
console.log(`Подход ${record.set} завершен`)
} else if (record.status === 0) { } else if (record.status === 0) {
// СТАТУС 0 = подход в процессе или на паузе // Незавершённый подход
lastIncompleteSet = record if (!lastIncompleteSet || record.set > lastIncompleteSet.set) {
lastSavedTime = record.time_users || "00:00" lastIncompleteSet = record;
console.log(`Подход ${record.set} в процессе, время: ${lastSavedTime}`) lastSavedTime = record.time_users || "00:00";
} }
}) }
});
// Восстанавливаем состояние на основе данных с сервера // Восстановление завершённых подходов
if (completedSetsFromServer.length > 0) { if (completedSetsFromServer.length > 0) {
console.log("Восстанавливаем завершенные подходы:", completedSetsFromServer) setCompletedSets(completedSetsFromServer);
setCompletedSets(completedSetsFromServer)
} }
// Если есть незавершенный подход, восстанавливаем его состояние // Восстановление незавершённого подхода
if (lastIncompleteSet) { if (lastIncompleteSet) {
console.log("Восстанавливаем незавершенный подход:", lastIncompleteSet.set) const timeInSeconds = convertTimeToSeconds(lastSavedTime);
// Конвертируем время из формата MM:SS в секунды setCurrentSet(lastIncompleteSet.set);
const timeInSeconds = convertTimeToSeconds(lastSavedTime) setCurrentTime(timeInSeconds);
setHasSavedProgress(true);
setIsCompleted(false);
setCurrentSet(lastIncompleteSet.set) // Устанавливаем текущий подход console.log(`Восстановлен незавершённый подход ${lastIncompleteSet.set} с временем ${timeInSeconds} секунд`);
setCurrentTime(timeInSeconds) // Восстанавливаем время
setHasSavedProgress(true) // Помечаем что есть сохраненный прогресс
setIsCompleted(false) // Упражнение не завершено
console.log(`Восстановлено состояние: подход ${lastIncompleteSet.set}, время ${timeInSeconds} секунд`)
} }
// Проверяем, завершены ли все подходы // Проверка, завершены ли все подходы
if (completedSetsFromServer.length >= totalSets) { if (completedSetsFromServer.length >= totalSets) {
console.log("ВСЕ ПОДХОДЫ ЗАВЕРШЕНЫ согласно серверу!") setIsCompleted(true);
setIsCompleted(true) // Помечаем упражнение как завершенное setCurrentTime(totalTime);
setCurrentTime(totalTime) // Устанавливаем время на максимум setHasSavedProgress(false);
setHasSavedProgress(false) // Сбрасываем флаг сохранения console.log("Все подходы завершены");
} }
return true // Successfully loaded from server return true;
} else { } else {
console.log("Прогресс на сервере не найден, начинаем с чистого листа") console.log("Прогресс не найден, начинаем с чистого листа");
return false // No progress found on server return false;
} }
} catch (error) { } catch (error) {
console.error("Ошибка при загрузке прогресса с сервера:", error) console.error("Ошибка при загрузке прогресса с сервера:", error);
return false // Failed to load from server 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,8 +746,7 @@ 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" : ""}`}
@ -782,8 +809,7 @@ 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"
}`} }`}
/> />
{/* Номер подхода под полоской */} {/* Номер подхода под полоской */}
@ -825,8 +851,7 @@ 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" : ""}`
@ -851,8 +876,7 @@ 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" ? "bg-gradient-to-r from-cyan-400 via-cyan-500 to-cyan-600"
: isResting : 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"
@ -889,8 +913,7 @@ 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"
}`} }`}
@ -937,8 +960,7 @@ 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"
}`} }`}

1025
src/pages/ExerciseOld.tsx Normal file

File diff suppressed because it is too large Load Diff