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
728 changes: 364 additions & 364 deletions src/components/modals/ChatbotSettingsModal.tsx

Large diffs are not rendered by default.

496 changes: 167 additions & 329 deletions src/components/modals/DislikedFoodsModal.tsx

Large diffs are not rendered by default.

534 changes: 534 additions & 0 deletions src/components/modals/PreferredFoodsModal.tsx

Large diffs are not rendered by default.

5,307 changes: 2,758 additions & 2,549 deletions src/screens/diet/MealRecommendScreen.tsx

Large diffs are not rendered by default.

3,719 changes: 1,735 additions & 1,984 deletions src/screens/diet/TempMealRecommendScreen.tsx

Large diffs are not rendered by default.

219 changes: 146 additions & 73 deletions src/services/userPreferencesAPI.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
// src/services/userPreferencesAPI.ts

import { requestAI } from "./apiConfig";
import type { ExclusionResponse, DeleteExclusionResponse } from "../types";
import type {
ExclusionResponse,
PreferenceResponse,
PreferenceDeleteResponse,
} from "../types";

export const userPreferencesAPI = {
/**
* 📋 비선호(제외) 식단 목록 조회
* GET /exclusions/{user_id}
* @param userId 사용자 ID (문자열, 예: "ehdrb")
* @returns 비선호 식단 목록 배열
*
* 응답 예시:
* [
* { id: 1, food_name: "굴비, 다랑어", reason: "taste" }
* ]
* 비선호 음식 목록 조회
*/
getExclusions: async (userId: string): Promise<ExclusionResponse[]> => {
getExclusions: async (userId: string) => {
try {
console.log(`📋 비선호 식단 조회 요청 (User: ${userId})`);
console.log("🚫 비선호 음식 목록 조회:", userId);

const response = await requestAI<ExclusionResponse[]>(
`/exclusions/${userId}`,
Expand All @@ -25,95 +22,171 @@ export const userPreferencesAPI = {
}
);

console.log("✅ 비선호 식단 조회 성공:", response);
console.log("✅ 비선호 음식 조회 완료:", response.length, "개");
return response;
} catch (error: any) {
console.error("❌ 비선호 식단 조회 실패:", error);
throw new Error(
error.message || "비선호 식단 목록을 불러오는데 실패했습니다."
if (error.status === 404) {
console.log("ℹ️ 비선호 음식 없음");
return [];
}
console.error("❌ 비선호 음식 조회 실패:", error);
throw error;
}
},

/**
* 비선호 음식 추가 (단일)
*/
addExclusion: async (userId: string, foodName: string) => {
try {
console.log("🚫 비선호 음식 추가:", foodName);

const response = await requestAI<ExclusionResponse>(
`/exclusions/${userId}?food_name=${encodeURIComponent(foodName)}`,
{
method: "POST",
}
);

console.log("✅ 비선호 음식 추가 완료:", response);
return response;
} catch (error: any) {
console.error("❌ 비선호 음식 추가 실패:", error);
throw new Error(error.message || "비선호 음식 추가에 실패했습니다.");
}
},

/**
* 🚫 비선호(제외) 식단 추가
* POST /exclusions/{user_id}?food_name=굴비&food_name=다랑어&reason=taste
* @param userId 사용자 ID (문자열)
* @param foods 추가할 음식 이름 배열 (예: ["굴비", "다랑어"])
* @returns 추가된 비선호 식단 정보
*
* 응답 예시:
* { id: 1, food_name: "굴비, 다랑어", reason: "taste" }
* 비선호 음식 추가 (여러 개) - 하나씩 순차 추가
*/
addExclusions: async (
userId: string,
foods: string[]
): Promise<ExclusionResponse> => {
addExclusions: async (userId: string, foodNames: string[]) => {
try {
console.log(`🚫 비선호 식단 추가 요청 (User: ${userId})`);
console.log("추가할 음식:", foods);

// 쿼리 파라미터 생성
const queryParams = new URLSearchParams();
foods.forEach((food) => {
queryParams.append("food_name", food);
});
queryParams.append("reason", "taste");

const queryString = queryParams.toString();
const url = `/exclusions/${userId}?${queryString}`;

// 📌 한글로 디코딩된 URL 로그 출력
const decodedUrl = `/exclusions/${userId}?${decodeURIComponent(
queryString
)}`;
console.log("🔗 요청 URL:", decodedUrl);
// 또는 더 명확하게
console.log(
`POST /exclusions/${userId}?${foods
.map((f) => `food_name=${f}`)
.join("&")}&reason=taste`
console.log("🚫 비선호 음식 여러 개 추가:", foodNames);

const results: ExclusionResponse[] = [];

// 각 음식을 하나씩 추가
for (const foodName of foodNames) {
console.log(` - "${foodName}" 추가 중...`);
const response = await requestAI<ExclusionResponse>(
`/exclusions/${userId}?food_name=${encodeURIComponent(foodName)}`,
{
method: "POST",
}
);
results.push(response);
console.log(` ✅ "${foodName}" 추가 완료 (id: ${response.id})`);
}

console.log("✅ 모든 비선호 음식 추가 완료:", results.length, "개");

// 마지막 결과 반환 (DislikedFoodsModal 호환성)
return results[results.length - 1];
} catch (error: any) {
console.error("❌ 비선호 음식 추가 실패:", error);
throw new Error(error.message || "비선호 음식 추가에 실패했습니다.");
}
},

/**
* 비선호 음식 삭제
*/
deleteExclusion: async (exclusionId: number) => {
try {
console.log("🗑️ 비선호 음식 삭제:", exclusionId);

const response = await requestAI<{ status: string }>(
`/exclusions/${exclusionId}`,
{
method: "DELETE",
}
);

const response = await requestAI<ExclusionResponse>(url, {
method: "POST",
});
console.log("✅ 비선호 음식 삭제 완료");
return response;
} catch (error: any) {
console.error("❌ 비선호 음식 삭제 실패:", error);
throw new Error(error.message || "비선호 음식 삭제에 실패했습니다.");
}
},

/**
* 선호하는 음식 목록 조회
*/
getPreferences: async (userId: string) => {
try {
console.log("💚 선호 음식 목록 조회:", userId);

console.log("✅ 비선호 식단 추가 성공:", response);
const response = await requestAI<PreferenceResponse[]>(
`/preferences/${userId}`,
{
method: "GET",
}
);

console.log("✅ 선호 음식 조회 완료:", response.length, "개");
return response;
} catch (error: any) {
console.error("❌ 비선호 식단 추가 실패:", error);
throw new Error(error.message || "비선호 식단 추가에 실패했습니다.");
if (error.status === 404) {
console.log("ℹ️ 선호 음식 없음");
return [];
}
console.error("❌ 선호 음식 조회 실패:", error);
throw error;
}
},

/**
* 🗑️ 비선호(제외) 식단 삭제
* DELETE /exclusions/{exclusion_id}
* @param exclusionId 비선호 식단 저장한 식단의 id (조회 시 받은 id 값)
* @returns 삭제 결과
*
* 응답 예시:
* { status: "deleted" }
* 선호하는 음식 추가 (여러 개) - 하나씩 순차 추가
*/
deleteExclusion: async (
exclusionId: number
): Promise<DeleteExclusionResponse> => {
addPreferences: async (userId: string, foodNames: string[]) => {
try {
console.log(`🗑️ 비선호 식단 삭제 요청 (ID: ${exclusionId})`);
console.log("💚 선호 음식 여러 개 추가:", foodNames);

const response = await requestAI<DeleteExclusionResponse>(
`/exclusions/${exclusionId}`,
const results: PreferenceResponse[] = [];

// 각 음식을 하나씩 추가
for (const foodName of foodNames) {
console.log(` - "${foodName}" 추가 중...`);
const response = await requestAI<PreferenceResponse>(
`/preferences/${userId}?food_name=${encodeURIComponent(foodName)}`,
{
method: "POST",
}
);
results.push(response);
console.log(` ✅ "${foodName}" 추가 완료 (id: ${response.id})`);
}

console.log("✅ 모든 선호 음식 추가 완료:", results.length, "개");

// 마지막 결과 반환
return results[results.length - 1];
} catch (error: any) {
console.error("❌ 선호 음식 추가 실패:", error);
throw new Error(error.message || "선호 음식 추가에 실패했습니다.");
}
},

/**
* 선호하는 음식 삭제
*/
deletePreference: async (preferenceId: number) => {
try {
console.log("🗑️ 선호 음식 삭제:", preferenceId);

const response = await requestAI<PreferenceDeleteResponse>(
`/preferences/${preferenceId}`,
{
method: "DELETE",
}
);

console.log("✅ 비선호 식단 삭제 성공:", response);
console.log("✅ 선호 음식 삭제 완료:", response);
return response;
} catch (error: any) {
console.error(`❌ 비선호 식단 삭제 실패 (ID: ${exclusionId}):`, error);
throw new Error(error.message || "비선호 식단 삭제에 실패했습니다.");
console.error("❌ 선호 음식 삭제 실패:", error);
throw new Error(error.message || "선호 음식 삭제에 실패했습니다.");
}
},
};

Choose a reason for hiding this comment

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

코드 리뷰 코멘트

  1. 에러 처리 변경:

    • getExclusionsgetPreferences에서 404 에러가 발생했을 때, 빈 배열을 반환하는 로직이 추가되었습니다. 이는 유용할 수 있지만, API 호출이 실제로 실패했을 때, 사용자가 이를 인지하지 못할 위험이 있습니다. 추가적인 로깅이나 통지를 고려해야 합니다.
  2. 리턴 타입 미지정:

    • getExclusionsgetPreferences 함수의 리턴 타입이 지정되어 있지 않습니다. TypeScript의 이점을 활용하기 위해, 정확한 반환 타입을 명시하는 것이 좋습니다. 예를 들어, Promise<ExclusionResponse[]>Promise<PreferenceResponse[]>와 같이 지정하면 더 명확합니다.
  3. 중복된 코드:

    • 비슷한 로직이 addExclusionsaddPreferences 두 곳에 존재합니다. 각각의 음식 이름을 추가하는 반복문을 공통 함수로 분리하면 코드의 중복을 줄이고 유지보수를 쉽게 할 수 있습니다.
  4. 에러 타입 불확실성:

    • 에러 처리 시 error의 타입이 any로 설정되어 있습니다. 보다 구체적인 에러 타입을 사용하면 에러 핸들링을 더욱 정확히 처리할 수 있습니다.
  5. 대기 시간 고려:

    • addExclusions에서 각 음식에 대한 API 호출을 순차적으로 진행하는 경우, 많은 음식이 있을 경우 비효율적일 수 있습니다. Promise.all을 사용하여 병렬로 API 호출할 수 있는 방법을 고려해볼 수 있습니다.
  6. 요청 내용의 유효성 검사 부족:

    • userIdfoodName이 유효한지, 혹은 NULL 체크가 필요합니다. 이 검사를 통해 API 호출 전에 불필요한 요청을 방지할 수 있습니다.

이 외에도, 주석에서 한국어와 영어가 혼합되어 있는 점, 일관성을 유지하기 위해 한국어로 통일하는 것이 좋습니다.

Loading