diff --git a/src/App.jsx b/src/App.jsx index a237256..6c78938 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,6 +5,7 @@ import Recipe from "./pages/recipe/Recipe" import RecipeDetail from "./pages/recipe/RecipeDetail" import {AddRecipe} from "./pages/recipe/AddRecipe" import Join from "./pages/user/Join" +import RecipeRecommend from "./pages/recipe/RecipeRecommend" function App() { return ( @@ -18,6 +19,7 @@ function App() { } /> } /> + } /> }/> } /> diff --git a/src/components/main/Header.jsx b/src/components/main/Header.jsx index 3099bd4..57931e6 100644 --- a/src/components/main/Header.jsx +++ b/src/components/main/Header.jsx @@ -13,6 +13,7 @@ function Header() { diff --git a/src/pages/recipe/Recipe.jsx b/src/pages/recipe/Recipe.jsx index 09a4249..b3298b6 100644 --- a/src/pages/recipe/Recipe.jsx +++ b/src/pages/recipe/Recipe.jsx @@ -1,12 +1,17 @@ import { useEffect, useState,useCallback } from "react" import { getRecipeList } from "../../sources/api/recipeAPI.jsx"; +import {Link, Route, useNavigate} from "react-router-dom"; +import { AddRecipe} from "./addRecipe.jsx"; import {Route, useNavigate} from "react-router-dom"; import RecipeItem from "../../components/recipe/RecipeItem.jsx" import "../../assets/css/recipe/recipe.css" + function Recipe() { const [recipeList, setRecipeList] = useState([]) const navigate = useNavigate(); + const userPk = localStorage.getItem('userPk'); + //userPk 를 어떻게 처리할지에 따라 바꿀듯합니다. useEffect(() => { const fetchRecipes = async () => { @@ -29,16 +34,19 @@ function Recipe() { return ( <>

레시피 페이지

+
+ + + 맞춤 레시피 보기 + +
{recipeList.map((recipe) => ( ))}
-
    -
  • - -
  • -
+ ) } diff --git a/src/pages/recipe/RecipeRecommend.css b/src/pages/recipe/RecipeRecommend.css new file mode 100644 index 0000000..b52c1ca --- /dev/null +++ b/src/pages/recipe/RecipeRecommend.css @@ -0,0 +1,112 @@ +.recipe-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 2rem; + padding: 2rem; +} + +.recipe-card { + background: white; + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + overflow: hidden; + transition: transform 0.2s ease; +} + +.recipe-card:hover { + transform: translateY(-5px); +} + +.recipe-image { + width: 100%; + height: 200px; + object-fit: cover; +} + +.recipe-content { + padding: 1.5rem; +} + +.recipe-title { + font-size: 1.25rem; + font-weight: 600; + color: #333; + margin-bottom: 0.5rem; +} + +.recipe-info { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.match-rate { + color: #3FA2F6; + font-weight: 600; +} + +.expiry-warning { + background: #ffebeb; + color: #ff4d4d; + padding: 0.5rem; + border-radius: 6px; + font-size: 0.9rem; +} + +.view-recipe-btn { + width: 100%; + background: #96C9F4; + color: white; + padding: 0.75rem; + border: none; + border-radius: 6px; + margin-top: 1rem; + cursor: pointer; + /* transition: background 0.2s ease; */ +} + +.view-recipe-btn:hover { + background: #3FA2F6; +} + +/* RecipeRecommend.css에 추가 */ +.ingredients-section { + background: #f8f9fa; + padding: 1rem; + border-radius: 8px; +} + +.ingredients-grid { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.ingredient-tag { + display: inline-flex; + align-items: center; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.875rem; + background: white; + border: 1px solid #e0e0e0; +} + +.ingredient-tag.necessary { + border-color: #3FA2F6; + color: #3FA2F6; +} + +.ingredient-tag.optional { + border-color: #96C9F4; + color: #96C9F4; +} + +.necessary-badge { + background: #3FA2F6; + color: white; + font-size: 0.75rem; + padding: 0.125rem 0.375rem; + border-radius: 10px; + margin-left: 0.5rem; +} \ No newline at end of file diff --git a/src/pages/recipe/RecipeRecommend.jsx b/src/pages/recipe/RecipeRecommend.jsx new file mode 100644 index 0000000..95aa74b --- /dev/null +++ b/src/pages/recipe/RecipeRecommend.jsx @@ -0,0 +1,94 @@ +// RecipeRecommend.jsx +import { useEffect, useState } from "react"; +import { getRecommendedRecipes } from "../../sources/api/recipeAPI"; +import { API_URL_HOST } from "../../sources/api/recipeAPI"; +import defaultImage from "../../assets/image/default.gif"; +import './RecipeRecommend.css'; +import { useParams } from "react-router-dom"; + +function RecipeRecommend() { + const [recommendedRecipes, setRecommendedRecipes] = useState([]); + const {userPk} = useParams(); + + useEffect(() => { + const fetchRecommendedRecipes = async () => { + try { + const data = await getRecommendedRecipes(userPk); + setRecommendedRecipes(data); + } catch (err) { + console.error("레시피 추천 로드 실패:", err); + } + }; + + fetchRecommendedRecipes(); + }, [userPk]); + + return ( +
+

+ 맞춤 레시피 추천 +

+ +
+ {recommendedRecipes.map(recipe => ( +
+ 0 + ? `${API_URL_HOST}/${recipe.mainImages[0].filePath}` + : defaultImage} + alt={recipe.recipeName} + className="recipe-image" + /> + +
+

{recipe.recipeName}

+

{recipe.recipeContent}

+ +
+

필요한 재료

+
+ {recipe.ingredients.map((ingredient, index) => ( + + {ingredient.ingredientName} + {ingredient.necessary && + 필수 + } + + ))} +
+
+ +
+
+ 조리 시간 + {recipe.recipeCookingTime}분 +
+ +
+ 재료 매칭률 + {recipe.matchRate.toFixed(1)}% +
+ + {recipe.remainExpirationDays <= 3 && ( +
+ ⚠️ {recipe.urgentIngredientName} + {recipe.remainExpirationDays}일 남음 +
+ )} +
+ + +
+
+ ))} +
+
+ ); +} + +export default RecipeRecommend; \ No newline at end of file diff --git a/src/sources/api/recipeAPI.jsx b/src/sources/api/recipeAPI.jsx index 2d68640..98670df 100644 --- a/src/sources/api/recipeAPI.jsx +++ b/src/sources/api/recipeAPI.jsx @@ -21,4 +21,10 @@ export const createRecipe = async (recipe) => { const url = `${prefix}`; const res = await axios.post(url, recipe); return res.data; +} + +export const getRecommendedRecipes = async (userPk) => { + const url = `${prefix}/recommend?userPk=${userPk}`; + const res = await axios.get(url); + return res.data; } \ No newline at end of file