Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 46 additions & 40 deletions src/screens/exercise/RoutineRecommendScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type ExercisePlan = {
planName: string;
createdAt: string;
description?: string;
days: Exercise[][]; // 날짜별 운동 배열 (Day 1, Day 2...)
days: Exercise[][];
isServerPlan: boolean;
};

Expand All @@ -46,23 +46,18 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
const [isEditMode, setIsEditMode] = useState(false);
const [selectedPlanIds, setSelectedPlanIds] = useState<string[]>([]);

// 🔄 데이터 로드 함수 (서버 API 연동)
// 데이터 로드 함수
// 🔄 데이터 로드 함수 (1일 + 7일 통합)
// 🔄 데이터 로드 함수
const loadExercisePlans = async () => {
try {
setLoading(true);

console.log("🔄 운동 데이터 로드 시작...");

// 1. 두 API를 동시에 호출 (병렬 처리)
const [dailyResponse, weeklyResponse] = await Promise.all([
// (1) 1일 추천 조회 (실패해도 빈 배열 반환하여 7일치는 보이게 함)
recommendedExerciseAPI.getRecommendedExercises().catch((err) => {
console.warn("⚠️ 1일 추천 조회 실패:", err);
return [];
}),
// (2) 7일 내역 조회 (실패해도 빈 객체 반환)
recommendedExerciseAPI.getRecommendedHistory().catch((err) => {
console.warn("⚠️ 7일 내역 조회 실패:", err);
return {};
Expand All @@ -71,13 +66,10 @@ const RoutineRecommendScreen = ({ navigation }: any) => {

let combinedPlans: ExercisePlan[] = [];

// ------------------------------------------------------------
// 🅰️ [1일 추천 데이터 처리] (기존 로직 복구)
// ------------------------------------------------------------
// 🅰️ [1일 추천 데이터 처리]
if (Array.isArray(dailyResponse) && dailyResponse.length > 0) {
console.log(`📥 1일 추천 데이터: ${dailyResponse.length}개`);

// 타겟별로 그룹화 (예: 가슴, 등...)
const groupedByTarget = dailyResponse.reduce(
(acc: any, exercise: any) => {
const target = exercise.target || "전신";
Expand All @@ -88,24 +80,21 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
{}
);

// 플랜 객체로 변환
const dailyPlans = Object.entries(groupedByTarget).map(
([target, exs]: [string, any], index) => ({
planId: `daily_${target}_${Date.now()}_${index}`,
planName: `오늘의 추천 - ${target}`,
createdAt: new Date().toISOString(), // 생성일 (오늘)
createdAt: new Date().toISOString(),
description: `${target} 집중 트레이닝 (1일)`,
days: [exs], // 1일치이므로 배열 안에 배열 하나 [[운동1, 운동2...]]
days: [exs],
isServerPlan: true,
})
);

combinedPlans = [...combinedPlans, ...dailyPlans];
}

// ------------------------------------------------------------
// 🅱️ [7일 내역 데이터 처리] (객체 형태 { "2025-12-14": [...] })
// ------------------------------------------------------------
// 🅱️ [7일 내역 데이터 처리]
if (
weeklyResponse &&
typeof weeklyResponse === "object" &&
Expand All @@ -118,10 +107,8 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
}개 감지`
);

// 1. 날짜 오름차순 정렬
const sortedDates = Object.keys(weeklyResponse).sort();

// 2. 날짜별 운동 리스트 추출
const daysArray = sortedDates.map((date) => {
const exercises = weeklyResponse[date] || [];
return exercises.map((ex: any) => ({
Expand All @@ -135,7 +122,6 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
}));
});

// 3. 통합 플랜 생성 (데이터가 있는 경우만)
if (daysArray.length > 0) {
const weeklyPlan: ExercisePlan = {
planId: `weekly_history_${Date.now()}`,
Expand All @@ -149,10 +135,7 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
}
}

// ------------------------------------------------------------
// 🏁 최종 합치기 및 정렬
// ------------------------------------------------------------
// 최신순 정렬 (생성일 기준 내림차순)
combinedPlans.sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
Expand Down Expand Up @@ -212,17 +195,49 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
}
};

// ✅ 수정된 멤버십 타입 확인 로직
const handleNewRecommendPress = async () => {
try {
setLoading(true);
console.log("🔍 멤버십 타입 확인 중...");

const profile = await authAPI.getProfile();
if (profile.membershipType === "FREE") {
console.log("📦 getProfile 응답:", JSON.stringify(profile, null, 2));

// profile이 직접 프로필 객체임 (profile.membershipType으로 바로 접근)
const membershipType = profile?.membershipType;
console.log("🎯 멤버십 타입:", membershipType);

// 멤버십 타입에 따른 네비게이션
if (!membershipType) {
console.warn("⚠️ 멤버십 타입이 없습니다. 기본값: 무료");
navigation.navigate("TempRoutineRecommendScreen");
} else {
} else if (membershipType.toUpperCase() === "FREE") {
console.log("➡️ 무료 페이지로 이동");
navigation.navigate("TempRoutineRecommendScreen");
} else if (membershipType.toUpperCase() === "PREMIUM") {
console.log("➡️ 유료 페이지로 이동");
navigation.navigate("RoutineRecommendNew");
} else {
// 예상치 못한 값인 경우
console.warn(`⚠️ 알 수 없는 멤버십 타입: ${membershipType}`);
navigation.navigate("TempRoutineRecommendScreen");
}
} catch (error) {
navigation.navigate("TempRoutineRecommendScreen");
} catch (error: any) {
console.error("❌ 멤버십 확인 중 오류:", error);
console.error("오류 상세:", error.message);

// 에러 발생 시 무료 버전으로 이동
Alert.alert(
"알림",
"멤버십 정보를 확인할 수 없습니다.\n무료 버전으로 이동합니다.",
[
{
text: "확인",
onPress: () => navigation.navigate("TempRoutineRecommendScreen"),
},
]
);
} finally {
setLoading(false);
}
Expand All @@ -241,9 +256,7 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
try {
setLoading(true);

// "나의 주간 운동 일정"인 경우
if (plan.planId.startsWith("weekly_history")) {
// 모든 날짜 추출
const dates = plan.days
.map((dayExercises) => dayExercises[0]?.date)
.filter(Boolean);
Expand All @@ -258,7 +271,6 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
let successCount = 0;
let failCount = 0;

// 각 날짜를 개별적으로 삭제
for (const date of dates) {
try {
console.log(`📅 ${date} 삭제 중...`);
Expand All @@ -267,7 +279,6 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
date
);

// 응답에 deletedCount 체크
if (result && result.deletedCount > 0) {
successCount++;
console.log(
Expand Down Expand Up @@ -296,9 +307,7 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
"삭제할 데이터가 없거나 삭제에 실패했습니다."
);
}
}
// "오늘의 추천"인 경우
else if (plan.planId.startsWith("daily_")) {
} else if (plan.planId.startsWith("daily_")) {
const today = new Date().toISOString().split("T")[0];
console.log(`📅 ${today} 삭제 중...`);

Expand All @@ -317,7 +326,6 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
return;
}

// 삭제 후 목록 새로고침
await loadExercisePlans();
} catch (error: any) {
console.error("❌ 삭제 중 오류:", error);
Expand All @@ -332,6 +340,7 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
},
]);
};

const handleBulkDelete = async () => {
if (selectedPlanIds.length === 0) return;

Expand Down Expand Up @@ -476,7 +485,7 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
<Text style={styles.emptyText}>저장된 추천 내역이 없습니다.</Text>
<TouchableOpacity
style={styles.goToRecommendBtn}
onPress={() => navigation.navigate("RoutineRecommendNew")}
onPress={handleNewRecommendPress}
>
<Text style={styles.goToRecommendBtnText}>
추천받으러 가기 →
Expand Down Expand Up @@ -620,7 +629,6 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
</Text>
</View>

{/* 7일 루틴일 경우에만 상단 탭 표시 */}
{selectedPlan.days.length > 1 && (
<ScrollView
horizontal
Expand All @@ -629,12 +637,10 @@ const RoutineRecommendScreen = ({ navigation }: any) => {
contentContainerStyle={styles.dayTabs}
>
{selectedPlan.days.map((dayExercises, index) => {
// 해당 day의 날짜 추출 (첫 번째 운동의 date 필드 사용)
const dateStr = dayExercises[0]?.date;
let displayText = `${index + 1}일차`; // 기본값
let displayText = `${index + 1}일차`;

if (dateStr) {
// 날짜를 파싱해서 "MM/DD (요일)" 형식으로 표시
const date = new Date(dateStr);
const month = date.getMonth() + 1;
const day = date.getDate();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 검토

  1. 주석의 필요성: 제거된 주석들이 주요 기능이나 로직을 설명하고 있었기 때문에, 그 주석들을 완전히 제거하는 대신 중요한 주석들은 유지하는 것이 좋습니다. 특히 코드의 의도나 복잡한 로직을 설명하는 데 도움이 될 수 있습니다.

  2. 예외 처리: loadExercisePlanshandleNewRecommendPress에서 API 호출 시 예외 처리 방법이 효과적이지만, 순수한 오류를 구분하지 않고 있기 때문에 더 구체적인 에러 처리가 필요할 수 있습니다. 예를 들어, 네트워크 오류와 JSON 파싱 오류를 다르게 처리하면 사용자 경험이 개선될 것입니다.

  3. 타입 안전성: any 타입의 사용을 피하고, 적절한 타입을 정의하는 것이 중요합니다. reduce와 같은 함수에서 객체의 구조를 명확하게 정의하여 타입 안전성을 높일 수 있습니다.

  4. API 응답의 신뢰성: weeklyResponsedailyResponse의 형식에 대한 가정을 하여 처리하고 있으므로, 꼭 필요한 모든 필드가 응답에 포함되어 있는지 체크하는 로직을 추가하여 예기치 않은 동작을 방지할 필요가 있습니다.

  5. 최신 상태 유지: loadExercisePlans 함수에서 데이터 로드 후 초기화 작업을 수행하거나, 상태를 업데이트 할 때 정확한 상태를 반영하고 있는지 확인하는 것이 중요합니다. 상태 업데이트가 비동기로 이루어지기 때문에 이 부분을 좀 더 신중하게 관리해야 합니다.

  6. 함수의 역할 분리: 너무 많은 로직이 loadExercisePlans 함수에 몰려있습니다. 단일 책임 원칙(SRP)을 준수하려면 각 기능을 별도의 작은 함수로 분리하는 것을 고려하세요. 이를 통해 코드 가독성과 유지보수성을 높일 수 있습니다.

Expand Down
52 changes: 0 additions & 52 deletions src/screens/main/MyPageScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -330,58 +330,6 @@ const MyPageScreen = ({ navigation }: any) => {

<View style={styles.separator} />

{/* 테스트 섹션 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>🧪 개발 테스트 (개발 전용)</Text>
<View style={styles.sectionLinks}>
{/* 멤버십 전환 버튼 */}
<TouchableOpacity
style={styles.linkItem}
onPress={handleToggleMembership}
>
<View style={styles.linkItemWithBadge}>
<Text
style={[
styles.linkText,
{
color:
currentMembershipType === "PREMIUM"
? "#FFD700"
: NEW_COLORS.accent,
},
]}
>
{currentMembershipType === "FREE" ? "🆓 → 💎" : "💎 → 🆓"}{" "}
무료/유료 전환
</Text>
<View
style={[
styles.statusBadge,
currentMembershipType === "PREMIUM" && styles.premiumBadge,
]}
>
<Text style={styles.statusBadgeText}>
{currentMembershipType === "FREE" ? "FREE" : "PREMIUM"}
</Text>
</View>
</View>
<Icon
name="swap-horizontal"
size={20}
color={
currentMembershipType === "PREMIUM"
? "#FFD700"
: NEW_COLORS.accent
}
/>
</TouchableOpacity>

<View style={styles.subSeparator} />
</View>
</View>

<View style={styles.separator} />

{/* 구독/결제 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>💳 구독/결제</Text>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치에는 몇 가지 잠재적인 문제가 있습니다:

  1. 삭제된 코드 블록: 테스트 섹션이 완전히 삭제되었습니다. 이는 개발 중에 중요할 수 있는 기능이 누락되었다는 의미이며, 코드 리뷰에서 반드시 시스템의 전체 기능에 미치는 영향에 대해 논의해야 합니다.

  2. UI 요소의 일관성: 삭제된 UI 요소가 더미 데이터에 기반해 개발 테스트를 가능하게 했을 가능성이 존재합니다. 개발자나 테스트를 진행하는 사람들이 해당 UI가 사라짐에 따라 혼란스러울 수 있습니다. 이로 인해 테스트의 용이성이 감소할 수 있습니다.

  3. 비즈니스 로직: 삭제된 코드 내의 handleToggleMembership 함수와 관련된 비즈니스 로직에 대한 주의가 필요합니다. 이 부분의 기능이 여전히 필요하다면, 코드의 다른 부분에서 여전히 호출되고 있는지 여부를 확인해야 합니다.

  4. 변수 접근 및 스타일 외부 요소: currentMembershipType과 같은 변수가 여전히 필요로 하는지 확인하십시오. 이 변수는 다른 조건과 결합되어 UI를 결정하는 데 사용되므로, 이와 관련된 로직이 코드의 다른 부분에서 잘 처리되고 있는지 점검해야 합니다.

  5. 주석 처리: 다른 코드 부분과의 관련성을 명확히 하기 위해 주석을 더욱 자세히 작성하는 것이 좋습니다. 예를 들어, 특정 섹션이 왜 삭제되었는지에 대한 설명이 있으면 좋을 것입니다.

따라서, 이 코드를 병합하기 전에 위의 문제들을 해결하고 추가적인 논의가 필요합니다.

Expand Down