From 7d3ccffcf5ad31d9255e37be72a295178c56d967 Mon Sep 17 00:00:00 2001 From: minseo23232 Date: Mon, 15 Dec 2025 13:21:17 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EC=9A=B4=EB=8F=99=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/screens/exercise/ExerciseScreen.tsx | 434 +++++++++++++++--------- 1 file changed, 273 insertions(+), 161 deletions(-) diff --git a/src/screens/exercise/ExerciseScreen.tsx b/src/screens/exercise/ExerciseScreen.tsx index 8818f7c..3c44f96 100644 --- a/src/screens/exercise/ExerciseScreen.tsx +++ b/src/screens/exercise/ExerciseScreen.tsx @@ -34,7 +34,10 @@ import { saveWorkoutTitle, fetchSavedWorkouts, } from "../../utils/exerciseApi"; -import type { SavedWorkoutGroup } from "../../utils/exerciseApi"; +import type { + SavedWorkoutGroup, + SavedWorkoutRecord, +} from "../../utils/exerciseApi"; import { getExerciseGoalSummary } from "../../utils/exerciseGoalApi"; import { eventBus } from "../../utils/eventBus"; import { useDate } from "../../contexts/DateContext"; @@ -506,7 +509,9 @@ const ExerciseScreen = ({ navigation }: any) => { Record >({}); // 달력에 표시할 운동 시간 데이터 (날짜별, 초 단위) - const [dailyWorkoutSeconds, setDailyWorkoutSeconds] = useState>({}); + const [dailyWorkoutSeconds, setDailyWorkoutSeconds] = useState< + Record + >({}); const [showMonthView, setShowMonthView] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); const [isIntroVisible, setIsIntroVisible] = useState(false); @@ -949,12 +954,7 @@ const ExerciseScreen = ({ navigation }: any) => { (async () => { try { const storedUserId = await AsyncStorage.getItem("userId"); - console.log( - "[EXERCISE][DEBUG] AsyncStorage에서 가져온 userId:", - storedUserId - ); setUserId(storedUserId); - console.log("[EXERCISE][DEBUG] state에 설정된 userId:", storedUserId); } finally { setUserIdLoaded(true); } @@ -990,9 +990,7 @@ const ExerciseScreen = ({ navigation }: any) => { // 오늘 진행률 로드 (게이지 표시용) const loadTodayProgress = React.useCallback(async () => { try { - console.log("[PROGRESS] 오늘 진행률 로드 시작"); const data = await fetchTodayProgress(); - console.log("[PROGRESS] 오늘 진행률 API 응답:", data); setTodayProgress(data); } catch (e) { console.error("[PROGRESS] 오늘 진행률 로드 실패:", e); @@ -1003,9 +1001,7 @@ const ExerciseScreen = ({ navigation }: any) => { // 주간 칼로리 합계 로드 (이번 주) const loadWeeklyCalories = React.useCallback(async () => { try { - console.log("[PROGRESS] 주간 진행률 로드 시작"); const data = await fetchWeeklyProgress(); - console.log("[PROGRESS] 주간 진행률 API 응답:", data); const progressArray = Array.isArray(data) ? data : []; setWeeklyProgress(progressArray); const sum = Array.isArray(data) @@ -1034,8 +1030,6 @@ const ExerciseScreen = ({ navigation }: any) => { // 오늘의 총 운동 시간 조회 const loadTodayWorkoutTime = React.useCallback(async () => { - console.log("[EXERCISE][TIME] loadTodayWorkoutTime 호출됨", { userId }); - let finalUserId: number | null = null; // 1. state의 userId를 숫자로 변환 시도 @@ -1043,28 +1037,16 @@ const ExerciseScreen = ({ navigation }: any) => { const parsed = parseInt(userId, 10); if (!isNaN(parsed)) { finalUserId = parsed; - console.log( - "[EXERCISE][TIME] state userId를 숫자로 변환:", - finalUserId - ); } } // 2. 숫자 변환 실패 시 AsyncStorage에서 가져오기 if (!finalUserId) { const userIdStr = await AsyncStorage.getItem("userId"); - console.log( - "[EXERCISE][TIME] AsyncStorage에서 가져온 userId:", - userIdStr - ); if (userIdStr && userIdStr.trim() !== "") { const parsed = parseInt(userIdStr, 10); if (!isNaN(parsed)) { finalUserId = parsed; - console.log( - "[EXERCISE][TIME] AsyncStorage userId를 숫자로 변환:", - finalUserId - ); } } } @@ -1083,7 +1065,6 @@ const ExerciseScreen = ({ navigation }: any) => { .join("") ); const payload = JSON.parse(jsonPayload); - console.log("[EXERCISE][TIME] JWT payload:", payload); // userPk를 우선 확인 (숫자 ID), 그 다음 userId, 마지막으로 sub if (payload.userPk) { @@ -1093,7 +1074,6 @@ const ExerciseScreen = ({ navigation }: any) => { : parseInt(payload.userPk, 10); if (!isNaN(parsed)) { finalUserId = parsed; - console.log("[EXERCISE][TIME] JWT에서 userPk 추출:", finalUserId); } } else if (payload.userId) { const parsed = @@ -1102,13 +1082,11 @@ const ExerciseScreen = ({ navigation }: any) => { : parseInt(payload.userId, 10); if (!isNaN(parsed)) { finalUserId = parsed; - console.log("[EXERCISE][TIME] JWT에서 userId 추출:", finalUserId); } } else if (payload.sub) { const parsed = parseInt(payload.sub, 10); if (!isNaN(parsed)) { finalUserId = parsed; - console.log("[EXERCISE][TIME] JWT에서 sub 추출:", finalUserId); } } } @@ -1118,20 +1096,11 @@ const ExerciseScreen = ({ navigation }: any) => { } if (!finalUserId) { - console.log("[EXERCISE][TIME] userId를 찾을 수 없음, 함수 종료"); return; } try { - console.log( - "[EXERCISE][TIME] getTodayWorkoutTime 호출 시작:", - finalUserId - ); const response = await getTodayWorkoutTime(finalUserId); - console.log("[EXERCISE][TIME] getTodayWorkoutTime 응답 받음:", { - totalSeconds: response.totalSeconds, - response, - }); setTodayTotalWorkoutSeconds(response.totalSeconds || 0); } catch (e) { console.error("[EXERCISE][TIME] 오늘 운동 시간 조회 실패:", e); @@ -1190,7 +1159,6 @@ const ExerciseScreen = ({ navigation }: any) => { .join("") ); const payload = JSON.parse(jsonPayload); - console.log("[EXERCISE][SAVED][DEBUG] JWT payload:", payload); // userPk를 우선 확인 (숫자 ID), 그 다음 userId, 마지막으로 sub if (payload.userPk) { @@ -1200,10 +1168,6 @@ const ExerciseScreen = ({ navigation }: any) => { : parseInt(payload.userPk, 10); if (!isNaN(parsed)) { finalUserId = parsed; - console.log( - "[EXERCISE][SAVED][DEBUG] JWT에서 userPk 추출:", - finalUserId - ); } } else if (payload.userId) { const parsed = @@ -1212,19 +1176,11 @@ const ExerciseScreen = ({ navigation }: any) => { : parseInt(payload.userId, 10); if (!isNaN(parsed)) { finalUserId = parsed; - console.log( - "[EXERCISE][SAVED][DEBUG] JWT에서 userId 추출:", - finalUserId - ); } } else if (payload.sub) { const parsed = parseInt(payload.sub, 10); if (!isNaN(parsed)) { finalUserId = parsed; - console.log( - "[EXERCISE][SAVED][DEBUG] JWT에서 sub 추출:", - finalUserId - ); } } } @@ -1281,12 +1237,27 @@ const ExerciseScreen = ({ navigation }: any) => { // savedWorkouts를 allActivities로 변환하여 추가 // 서버 데이터를 기준으로 현재 날짜의 저장된 운동을 재구성 + + // 이미지 로드를 위한 정보 수집 (setAllActivities 내부에서 수집) + let imageLoadTasks: Array<{ + activityId: number; + externalId?: string; + exerciseName: string; + }> = []; + setAllActivities((prev) => { const currentDateStr = dateStr; const newActivities: Activity[] = []; const serverSessionIds = new Set(); const serverActivityKeys = new Set(); + // 이미지 로드 태스크 수집 (이 함수 내부에서) + const tasks: Array<{ + activityId: number; + externalId?: string; + exerciseName: string; + }> = []; + // 서버에서 가져온 데이터로 Activity 생성 mergedGroups.forEach((group) => { const normalizedTitle = (group.title || "").trim() || "운동 기록"; @@ -1338,8 +1309,9 @@ const ExerciseScreen = ({ navigation }: any) => { session.externalId || undefined; + const activityId = Date.now() + Math.random(); const activity: Activity = { - id: Date.now() + Math.random(), // 고유 ID 생성 + id: activityId, // 고유 ID 생성 name: exerciseName, details: buildDetailsFromSets(sets), time: firstRecord.workoutDate @@ -1354,6 +1326,15 @@ const ExerciseScreen = ({ navigation }: any) => { externalId: externalId, }; + // 이미지 로드가 필요한 경우 태스크 추가 (exerciseName이 있으면 무조건 추가) + if (exerciseName && exerciseName.trim()) { + tasks.push({ + activityId, + externalId, + exerciseName: exerciseName.trim(), + }); + } + if (__DEV__ && !externalId) { console.warn("[EXERCISE][SAVED] externalId 없음:", { exerciseName, @@ -1465,23 +1446,177 @@ const ExerciseScreen = ({ navigation }: any) => { result.push(activity); }); - console.log("[EXERCISE][SAVED] 저장된 운동 기록 재구성:", { - prevCount: prev.length, - filteredPrevCount: filteredPrev.length, - newCount: newActivities.length, - resultCount: result.length, - removedCount: prev.length - filteredPrev.length, - addedCount: result.length - filteredPrev.length, - activities: newActivities.map((a) => ({ - name: a.name, - sessionId: a.sessionId, - setsCount: a.sets?.length || 0, - })), - }); + if (__DEV__) { + console.log("[EXERCISE][SAVED] 저장된 운동 기록 재구성:", { + prevCount: prev.length, + newCount: newActivities.length, + resultCount: result.length, + imageLoadTasksCount: tasks.length, + }); + } + + // 이미지 로드 태스크를 외부 변수에 할당 + imageLoadTasks = tasks; return result; }); + // 저장된 운동의 이미지 로드 (병렬 처리) + // setAllActivities 완료 후 실행되도록 setTimeout 사용 + // imageLoadTasks는 setAllActivities 내부에서 할당되므로, 약간의 지연 후 확인 + setTimeout(() => { + // imageLoadTasks가 할당되었는지 확인 + if (imageLoadTasks.length > 0) { + if (__DEV__) { + console.log( + "[EXERCISE][SAVED] 이미지 로드 시작:", + imageLoadTasks.length, + "개" + ); + } + const loadImages = async () => { + const imageLoadPromises = imageLoadTasks.map(async (task) => { + try { + let imageUrl: string | undefined; + + // 1. externalId로 이미지 로드 시도 + if (task.externalId) { + // 현재 캐시 확인 + setExerciseImages((currentImages) => { + if (currentImages[task.externalId!]) { + imageUrl = currentImages[task.externalId!]; + return currentImages; // 변경 없음 + } + return currentImages; + }); + + if (!imageUrl) { + try { + const detail = await fetchExerciseDetail(task.externalId); + imageUrl = + detail?.imageUrl || + detail?.image || + detail?.imgUrl || + detail?.photoUrl; + + if (imageUrl) { + // 캐시에 저장 + setExerciseImages((prev) => ({ + ...prev, + [task.externalId!]: imageUrl!, + })); + prefetchImage(imageUrl); + } + } catch (error) { + console.warn( + "[EXERCISE][SAVED] externalId로 이미지 로드 실패:", + { + externalId: task.externalId, + exerciseName: task.exerciseName, + error: error instanceof Error ? error.message : error, + } + ); + } + } + } + + // 2. externalId로 실패했거나 없으면 이름으로 검색 + if (!imageUrl && task.exerciseName) { + const nameKey = task.exerciseName.toLowerCase(); + + // 현재 캐시 확인 + setExerciseImagesByName((currentImagesByName) => { + if (currentImagesByName[nameKey]) { + imageUrl = currentImagesByName[nameKey]; + return currentImagesByName; // 변경 없음 + } + return currentImagesByName; + }); + + if (!imageUrl) { + try { + const keywords = generateSearchKeywords( + task.exerciseName + ); + const keywordList = + keywords && keywords.length > 0 + ? keywords + : [task.exerciseName]; + + for (const keyword of keywordList) { + try { + const response = await fetchExercises({ + keyword, + size: 1, + page: 0, + }); + const first = response?.content?.[0]; + imageUrl = + first?.imageUrl || + first?.image || + first?.imgUrl || + first?.photoUrl; + + if (imageUrl) { + // 캐시에 저장 + setExerciseImagesByName((prev) => ({ + ...prev, + [nameKey]: imageUrl!, + })); + prefetchImage(imageUrl); + break; + } + } catch (error) { + // 다음 키워드 시도 + } + } + } catch (error) { + console.warn( + "[EXERCISE][SAVED] 이름으로 이미지 검색 실패:", + { + exerciseName: task.exerciseName, + error: error instanceof Error ? error.message : error, + } + ); + } + } + } + + // 3. 이미지를 찾았으면 Activity 업데이트 + if (imageUrl) { + setAllActivities((prev) => + prev.map((activity) => + activity.id === task.activityId + ? { ...activity, imageUrl } + : activity + ) + ); + } else { + console.warn("[EXERCISE][SAVED] 이미지를 찾지 못함:", { + activityId: task.activityId, + externalId: task.externalId, + exerciseName: task.exerciseName, + }); + } + } catch (error) { + console.warn("[EXERCISE][SAVED] 이미지 로드 중 오류:", { + task, + error: error instanceof Error ? error.message : error, + }); + } + }); + + // 모든 이미지 로드 완료 대기 (병렬 처리) + await Promise.all(imageLoadPromises); + }; + + // 비동기로 이미지 로드 시작 + loadImages().catch((error) => { + console.error("[EXERCISE][SAVED] 이미지 로드 실패:", error); + }); + } + }, 200); // setAllActivities 완료 대기 (100ms -> 200ms로 증가) + const sessionTitleMapFresh = new Map(); // filteredGroups 대신 mergedGroups 사용 (모든 세션 정보는 유지) mergedGroups.forEach((group) => { @@ -1545,11 +1680,29 @@ const ExerciseScreen = ({ navigation }: any) => { } catch (error) { console.error("[WORKOUT][SAVED] 불러오기 실패:", error); setSavedWorkouts([]); - setSavedWorkoutsError("저장된 운동을 불러오지 못했어요."); + // 네트워크 오류나 서버 오류인 경우에만 에러 메시지 표시 + // 빈 배열은 정상적인 응답일 수 있음 (저장된 운동이 없는 경우) + if (error && typeof error === "object" && "status" in error) { + const status = (error as any).status; + if (status && status !== 200 && status !== 404) { + setSavedWorkoutsError("저장된 운동을 불러오지 못했어요."); + } else { + setSavedWorkoutsError(null); // 404는 정상 (저장된 운동 없음) + } + } else { + setSavedWorkoutsError("저장된 운동을 불러오지 못했어요."); + } } finally { setSavedWorkoutsLoading(false); } - }, [userId, selectedDate]); + }, [ + userId, + selectedDate, + exerciseImages, + exerciseImagesByName, + prefetchImage, + generateSearchKeywords, + ]); React.useEffect(() => { if (!userIdLoaded) return; @@ -2605,16 +2758,11 @@ const ExerciseScreen = ({ navigation }: any) => { (activity) => !isActivityFullyCompleted(activity) ); - console.log("🏃 시작 가능한 운동:", availableActivities.length); - if (availableActivities.length === 0) { - console.log("⚠️ 시작 가능한 운동이 없어서 운동 추가 모달 열기"); handleWorkoutStartPress(); return; } - console.log("✅ 운동 시작:", availableActivities[0].name); - // 운동 시작 시점의 누적 시간 기록 setWorkoutStartTime(todayTotalWorkoutSeconds); setCurrentExerciseStartTime(todayTotalWorkoutSeconds); @@ -2914,7 +3062,6 @@ const ExerciseScreen = ({ navigation }: any) => { const parsed: Activity[] = JSON.parse(saved); if (Array.isArray(parsed)) { setAllActivities(parsed); - console.log("✅ [LOAD] 저장된 운동 불러오기 완료"); setInitialLoadComplete(true); // 로딩 완료 신호! return; } @@ -2950,13 +3097,10 @@ const ExerciseScreen = ({ navigation }: any) => { const recommendedExercises = rawParams.recommendedExercises || rawParams.params?.recommendedExercises; - const autoStart = - rawParams.autoStart || - rawParams.params?.autoStart; // 홈에서 운동 시작하기 버튼 클릭 시 전달되는 플래그 + const autoStart = rawParams.autoStart || rawParams.params?.autoStart; // 홈에서 운동 시작하기 버튼 클릭 시 전달되는 플래그 // 홈에서 운동 시작하기 버튼 클릭 시 자동으로 시작 if (autoStart) { - console.log("🏋️ [홈] 자동 운동 시작 요청"); // 파라미터 초기화 (재실행 방지) navigation.setParams({ autoStart: undefined, @@ -3008,16 +3152,9 @@ const ExerciseScreen = ({ navigation }: any) => { ); if (alreadyExists) { - console.log("⚠️ [AI] 이미 추가된 루틴입니다."); return prev; } - console.log("✅ [AI] 리스트에 운동 추가 완료!"); - console.log( - "🔍 추가 후 전체 개수:", - prev.length + recommendedExercises.length - ); - return [...prev, ...recommendedExercises]; }); }, 100); // 👈 100ms 딜레이 추가 @@ -3035,7 +3172,12 @@ const ExerciseScreen = ({ navigation }: any) => { ); }, 500); } - }, [initialLoadComplete, route.params, selectedDate, handleStartWorkoutSequence]) + }, [ + initialLoadComplete, + route.params, + selectedDate, + handleStartWorkoutSequence, + ]) ); // (임시 API 테스트 버튼 제거) @@ -3043,10 +3185,6 @@ const ExerciseScreen = ({ navigation }: any) => { const handleExerciseClick = (exercise: Activity) => { // 저장된 운동(saveTitle이 있는)은 클릭해도 모달을 열지 않음 (완료 상태로 유지) if (exercise.saveTitle && exercise.saveTitle.trim() !== "") { - console.log("[EXERCISE] 저장된 운동은 클릭 불가:", { - name: exercise.name, - saveTitle: exercise.saveTitle, - }); return; } @@ -3561,10 +3699,11 @@ const ExerciseScreen = ({ navigation }: any) => { // 운동 저장 후 서버에서 최신 데이터를 가져와서 중복 방지 및 일관성 유지 if (!skipServerSave && serverSessionId) { // 약간의 지연을 두어 서버에 반영될 시간을 줌 + // 저장 직후 조회 시 서버가 아직 반영하지 못할 수 있으므로 지연 시간 증가 setTimeout(async () => { loadSavedWorkouts(); await loadTodayProgress(); // 오늘 진행률 로드 (게이지 업데이트) - }, 800); + }, 1200); // 800ms -> 1200ms로 증가 } // sessionId 반환 (ExerciseModal에서 사용) @@ -3841,16 +3980,19 @@ const ExerciseScreen = ({ navigation }: any) => { dayProgress?.totalCalorie ?? 0; // 해당 날짜의 운동 시간(초) 가져오기 - const totalSeconds = dailyWorkoutSeconds[dateStr] ?? 0; - + const totalSeconds = + dailyWorkoutSeconds[dateStr] ?? 0; + // 운동 시간 포맷: 초만 있으면 "38s", 분이면 "39m", 60분 이상이면 "1h 39m" - const formatWorkoutTime = (seconds: number): string => { - if (seconds === 0) return ''; - + const formatWorkoutTime = ( + seconds: number + ): string => { + if (seconds === 0) return ""; + const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; - + if (hours > 0) { // 60분 이상: "1h 39m" return `${hours}h ${minutes}m`; @@ -3958,15 +4100,15 @@ const ExerciseScreen = ({ navigation }: any) => { 0; // 해당 날짜의 운동 시간(초) 가져오기 const totalSeconds = dailyWorkoutSeconds[dateStr] ?? 0; - + // 운동 시간 포맷: 초만 있으면 "38s", 분이면 "39m", 60분 이상이면 "1h 39m" const formatWorkoutTime = (seconds: number): string => { - if (seconds === 0) return ''; - + if (seconds === 0) return ""; + const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; - + if (hours > 0) { // 60분 이상: "1h 39m" return `${hours}h ${minutes}m`; @@ -4038,48 +4180,26 @@ const ExerciseScreen = ({ navigation }: any) => { - {/* 🔍 디버깅: 조건 확인 */} - {(() => { - const shouldShow = - hasIncompleteActivities && workoutActivities.length > 0; - console.log("🚨 시작 버튼 렌더링 체크:", { - hasIncompleteActivities, - workoutActivitiesLength: workoutActivities.length, - shouldShow, - }); - - if (shouldShow) { - console.log("✅ 시작 버튼 렌더링됨!"); - } else { - console.log("❌ 시작 버튼 조건 불만족"); - } - - return shouldShow ? ( - 0 && ( + { + handleStartWorkoutSequence(); + }} + > + { - console.log("🎯🎯🎯 시작 버튼 클릭됨!"); - console.log( - "workoutActivities:", - workoutActivities.length - ); - handleStartWorkoutSequence(); - }} > - - 시작 - - - ) : null; - })()} + 시작 + + + )} @@ -4370,14 +4490,12 @@ const ExerciseScreen = ({ navigation }: any) => { return !alreadySavedBySession; }); - console.log("[EXERCISE][COMPLETE] 필터링 결과:", { - totalExercises: exercises.length, - filteredCount: filteredExercises.length, - filtered: filteredExercises.map((ex) => ({ - name: ex.name, - sessionId: ex.sessionId, - })), - }); + if (__DEV__) { + console.log("[EXERCISE][COMPLETE] 필터링 결과:", { + totalExercises: exercises.length, + filteredCount: filteredExercises.length, + }); + } setCompletedExercises(filteredExercises); setCompletionSummaryTitle("오늘의 운동"); @@ -4398,9 +4516,10 @@ const ExerciseScreen = ({ navigation }: any) => { // 운동 완료 후 서버에서 최신 데이터를 가져와서 중복 방지 및 일관성 유지 // allActivities는 loadSavedWorkouts에서 자동으로 업데이트됨 + // 저장 직후 조회 시 서버가 아직 반영하지 못할 수 있으므로 지연 시간 증가 setTimeout(() => { loadSavedWorkouts(); - }, 500); + }, 1000); // 500ms -> 1000ms로 증가 handleModalClose(); }} @@ -4725,20 +4844,13 @@ const ExerciseScreen = ({ navigation }: any) => { ex.externalId && ex.name ? `${ex.externalId}__${ex.name}` : null; + // savedSessionIds만 확인 (서버에서 실제로 저장된 것만 제외) + // pendingSessionIds는 확인하지 않음 (방금 저장한 운동도 표시해야 함) const alreadySavedBySession = - sessionId && - (savedSessionIds.has(sessionId) || - pendingSessionIds.has(sessionId)); - const alreadySavedByActivity = - typeof activityId === "number" && - pendingActivityIds.has(activityId); - const alreadySavedByExternal = - externalKey && pendingExternalKeys.has(externalKey); - return !( - alreadySavedBySession || - alreadySavedByActivity || - alreadySavedByExternal - ); + sessionId && savedSessionIds.has(sessionId); + // pendingActivityIds와 pendingExternalKeys도 확인하지 않음 + // 운동 완료 모달에서는 완료된 운동을 모두 표시 + return !alreadySavedBySession; } ); From bab163d252bd5c94ad4c6c9c7a1a2b6bb3751cf7 Mon Sep 17 00:00:00 2001 From: minseo23232 Date: Mon, 15 Dec 2025 15:57:34 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/screens/exercise/ExerciseScreen.tsx | 206 +++++++++++------------- 1 file changed, 93 insertions(+), 113 deletions(-) diff --git a/src/screens/exercise/ExerciseScreen.tsx b/src/screens/exercise/ExerciseScreen.tsx index 3c44f96..b4a661e 100644 --- a/src/screens/exercise/ExerciseScreen.tsx +++ b/src/screens/exercise/ExerciseScreen.tsx @@ -940,7 +940,9 @@ const ExerciseScreen = ({ navigation }: any) => { }; loadActivityImages(); - }, [activities, exerciseImages, exerciseImagesByName, prefetchImage]); + // exerciseImages, exerciseImagesByName는 이미지가 로드될 때마다 변경되므로 + // 의존성에서 제거하여 불필요한 재실행 방지 (이미지가 필요한 활동만 체크) + }, [activities, prefetchImage]); const COMPLETED_COUNT_KEY_BASE = "workoutCompletedThisWeek"; const ACTIVITIES_KEY_BASE = "user_activities_v1"; @@ -1013,14 +1015,12 @@ const ExerciseScreen = ({ navigation }: any) => { const completedDays = progressArray.filter( (item) => item?.exerciseRate === 100 ); - console.log( - "[PROGRESS] exerciseRate가 100인 날짜:", - completedDays.map((d) => d.date) - ); - console.log( - "[PROGRESS] exerciseRate가 100인 날짜 개수:", - completedDays.length - ); + if (__DEV__ && completedDays.length > 0) { + console.log( + "[PROGRESS] 주간 완료:", + `${completedDays.length}일 / 총 ${progressArray.length}일, 칼로리: ${sum}kcal` + ); + } } catch (e) { console.error("주간 칼로리 로드 실패:", e); setWeeklyCalories(0); @@ -1698,10 +1698,8 @@ const ExerciseScreen = ({ navigation }: any) => { }, [ userId, selectedDate, - exerciseImages, - exerciseImagesByName, - prefetchImage, - generateSearchKeywords, + // exerciseImages, exerciseImagesByName 제거 - 이미지 로드는 별도 useEffect에서 처리 + // prefetchImage, generateSearchKeywords는 안정적인 함수이므로 제거 ]); React.useEffect(() => { @@ -1729,95 +1727,83 @@ const ExerciseScreen = ({ navigation }: any) => { ]); // 기록하기 탭에 들어올 때마다 이번 주 진행률을 다시 계산 + // 주의: useEffect에서 이미 loadWeeklyCalories()를 호출하므로, + // useFocusEffect에서는 completedCount만 업데이트하고 API 호출은 최소화 useFocusEffect( React.useCallback(() => { if (!userIdLoaded) return; - const refreshThisWeekProgress = async () => { - try { - // 서버에서 이번 주 진행률 데이터 가져오기 - const weeklyData = await fetchWeeklyProgress(); - if (!Array.isArray(weeklyData)) return; - - // 이번 주 시작일 계산 - const now = new Date(); - const thisWeekStart = new Date(now); - thisWeekStart.setDate(now.getDate() - now.getDay()); // 일요일로 설정 - thisWeekStart.setHours(0, 0, 0, 0); - - const today = new Date(); - const todayEnd = new Date( - today.getFullYear(), - today.getMonth(), - today.getDate(), - 23, - 59, - 59, - 999 - ); + // weeklyProgress가 이미 있으면 그것을 사용하여 completedCount만 업데이트 + // API 호출은 useEffect에서 이미 했으므로 중복 호출 방지 + const updateCompletedCount = () => { + if (!weeklyProgress || weeklyProgress.length === 0) return; - // 이번 주 완료된 날짜 개수 계산 (exerciseRate가 100인 날짜) - const completedDates = new Set(); - weeklyData.forEach((item) => { - if (!item || !item.date) return; - try { - const itemDate = new Date(item.date); - if (isNaN(itemDate.getTime())) return; + // 이번 주 시작일 계산 + const now = new Date(); + const thisWeekStart = new Date(now); + thisWeekStart.setDate(now.getDate() - now.getDay()); // 일요일로 설정 + thisWeekStart.setHours(0, 0, 0, 0); - const itemDateOnly = new Date( - itemDate.getFullYear(), - itemDate.getMonth(), - itemDate.getDate() - ); - const weekStartOnly = new Date( - thisWeekStart.getFullYear(), - thisWeekStart.getMonth(), - thisWeekStart.getDate() - ); - const todayEndOnly = new Date( - todayEnd.getFullYear(), - todayEnd.getMonth(), - todayEnd.getDate() - ); + const today = new Date(); + const todayEnd = new Date( + today.getFullYear(), + today.getMonth(), + today.getDate(), + 23, + 59, + 59, + 999 + ); - // 이번 주 범위 내에 있고, exerciseRate가 100이면 완료된 날짜로 간주 - if ( - itemDateOnly >= weekStartOnly && - itemDateOnly <= todayEndOnly && - item.exerciseRate === 100 - ) { - const dateKey = `${itemDateOnly.getFullYear()}-${String( - itemDateOnly.getMonth() + 1 - ).padStart(2, "0")}-${String(itemDateOnly.getDate()).padStart( - 2, - "0" - )}`; - completedDates.add(dateKey); - } - } catch (error) { - // 날짜 파싱 에러 무시 - } - }); + // 이번 주 완료된 날짜 개수 계산 (exerciseRate가 100인 날짜) + const completedDates = new Set(); + weeklyProgress.forEach((item) => { + if (!item || !item.date) return; + try { + const itemDate = new Date(item.date); + if (isNaN(itemDate.getTime())) return; - const actualCompletedThisWeek = completedDates.size; + const itemDateOnly = new Date( + itemDate.getFullYear(), + itemDate.getMonth(), + itemDate.getDate() + ); + const weekStartOnly = new Date( + thisWeekStart.getFullYear(), + thisWeekStart.getMonth(), + thisWeekStart.getDate() + ); + const todayEndOnly = new Date( + todayEnd.getFullYear(), + todayEnd.getMonth(), + todayEnd.getDate() + ); - // completedThisWeek 업데이트 - setCompletedCountPersist(actualCompletedThisWeek); + // 이번 주 범위 내에 있고, exerciseRate가 100이면 완료된 날짜로 간주 + if ( + itemDateOnly >= weekStartOnly && + itemDateOnly <= todayEndOnly && + item.exerciseRate === 100 + ) { + const dateKey = `${itemDateOnly.getFullYear()}-${String( + itemDateOnly.getMonth() + 1 + ).padStart(2, "0")}-${String(itemDateOnly.getDate()).padStart( + 2, + "0" + )}`; + completedDates.add(dateKey); + } + } catch (error) { + // 날짜 파싱 에러 무시 + } + }); - // weeklyProgress도 업데이트 - setWeeklyProgress(weeklyData); - const sum = weeklyData.reduce( - (s: number, d) => s + Number(d?.totalCalorie || 0), - 0 - ); - setWeeklyCalories(sum); - } catch (error) { - console.error("[PROGRESS] 이번 주 진행률 새로고침 실패:", error); - } + const actualCompletedThisWeek = completedDates.size; + setCompletedCountPersist(actualCompletedThisWeek); }; - refreshThisWeekProgress(); - }, [userIdLoaded, getStorageKey]) + updateCompletedCount(); + }, [userIdLoaded, weeklyProgress]) // weeklyProgress가 변경될 때만 업데이트 ); // 날짜를 yyyy-MM-dd 형식으로 변환 @@ -2279,20 +2265,23 @@ const ExerciseScreen = ({ navigation }: any) => { loadTodayProgress(); // 오늘 진행률 로드 (게이지 업데이트) // 운동 제목 저장 후 주간 진행률을 다시 가져와서 게이지 업데이트 - // 서버에서 exerciseRate 계산에 시간이 걸릴 수 있으므로 여러 번 재시도 + // 서버에서 exerciseRate 계산에 시간이 걸릴 수 있으므로 재시도 (최적화: 중복 호출 제거) const retryLoadProgress = async ( retryCount: number = 0, - maxRetries: number = 3 + maxRetries: number = 2 // 3 -> 2로 감소 ) => { try { - // 목표 데이터와 주간 진행률을 함께 업데이트 - await loadGoalData(); - await loadWeeklyCalories(); - await loadTodayProgress(); // 오늘 진행률 로드 (게이지 업데이트) + // 첫 번째 호출만 전체 데이터 로드 + if (retryCount === 0) { + await loadGoalData(); + await loadWeeklyCalories(); + await loadTodayProgress(); + } // exerciseRate가 업데이트되었는지 확인하기 위해 잠시 대기 후 다시 확인 setTimeout(async () => { try { + // 재시도 시에는 주간 진행률만 확인 (중복 호출 최소화) const freshData = await fetchWeeklyProgress(); if (Array.isArray(freshData)) { setWeeklyProgress(freshData); @@ -2302,9 +2291,6 @@ const ExerciseScreen = ({ navigation }: any) => { ); setWeeklyCalories(sum); - // 오늘 진행률도 다시 로드 (게이지 업데이트) - await loadTodayProgress(); - // 오늘 날짜의 exerciseRate 확인 const today = new Date(); const todayStr = formatDateToString(today); @@ -2320,15 +2306,10 @@ const ExerciseScreen = ({ navigation }: any) => { ) { setTimeout(() => { retryLoadProgress(retryCount + 1, maxRetries); - }, 2000 * (retryCount + 1)); // 재시도마다 대기 시간 증가 (2초, 4초, 6초) - } else if ( - todayProgressItem && - todayProgressItem.exerciseRate === 100 - ) { - console.log( - "[PROGRESS] exerciseRate 업데이트 확인됨:", - todayProgressItem - ); + }, 2000 * (retryCount + 1)); // 재시도마다 대기 시간 증가 (2초, 4초) + } else { + // 완료되었거나 재시도 횟수 초과 시 오늘 진행률만 한 번 더 확인 + await loadTodayProgress(); } } } catch (error) { @@ -3571,14 +3552,13 @@ const ExerciseScreen = ({ navigation }: any) => { } }); - // 주간 진행률 전체를 다시 가져와서 게이지 업데이트 - // 약간의 지연을 두어 서버에 반영될 시간을 줌 + // 주간 진행률 업데이트는 이미 위에서 처리했으므로 + // 오늘 진행률만 업데이트 (중복 호출 방지) setTimeout(async () => { try { - await loadWeeklyCalories(); - await loadTodayProgress(); // 오늘 진행률 로드 (게이지 업데이트) + await loadTodayProgress(); // 오늘 진행률만 로드 (게이지 업데이트) } catch (error) { - console.error("[PROGRESS] 주간 진행률 새로고침 실패:", error); + console.error("[PROGRESS] 오늘 진행률 새로고침 실패:", error); } }, 500);