diff --git a/src/components/meal-plan/CalendarChip/CalendarChipM.tsx b/src/components/chip/CalendarChip/CalendarChipM.tsx similarity index 100% rename from src/components/meal-plan/CalendarChip/CalendarChipM.tsx rename to src/components/chip/CalendarChip/CalendarChipM.tsx diff --git a/src/components/meal-plan/CalendarChip/CalendarChipS.tsx b/src/components/chip/CalendarChip/CalendarChipS.tsx similarity index 100% rename from src/components/meal-plan/CalendarChip/CalendarChipS.tsx rename to src/components/chip/CalendarChip/CalendarChipS.tsx diff --git a/src/components/meal-plan/MenuChip.tsx b/src/components/chip/MenuChip.tsx similarity index 100% rename from src/components/meal-plan/MenuChip.tsx rename to src/components/chip/MenuChip.tsx diff --git a/src/components/common/Footer.tsx b/src/components/common/Footer.tsx deleted file mode 100644 index bba2abbd..00000000 --- a/src/components/common/Footer.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import Background from "../../assets/icons/footer-background.svg"; -import Home from "../../assets/icons/home.svg"; -import Note from "../../assets/icons/note-edit.svg"; -import { useNavigate } from "react-router-dom"; -import CalendarIcon from "./icon/Calendar"; - -export default function Footer() { - const navigate = useNavigate(); - - return ( -
-
- 푸터 배경 - -
-
- -
- -
- -
-
- - -
-
- ); -} diff --git a/src/components/meal-plan/BottomSheet.tsx b/src/components/meal-plan/BottomSheet.tsx deleted file mode 100644 index 1a1590ef..00000000 --- a/src/components/meal-plan/BottomSheet.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import Button from "../common/Button"; -import { useEffect, useState } from "react"; -import MenuList from "../common/MenuList"; -import AlertModal from "../common/AlertModal"; -import useGetInfiniteFamilyWishList from "../../hooks/queries/use-get-infinite-family-wishlist"; -import { useFamilyStore } from "../../stores/use-family-store"; -import { useInView } from "react-intersection-observer"; -import { useGetRecommendList } from "../../hooks/queries/use-get-recommendations"; -import Chevron from "../common/icon/Chevron"; -import { useNavigate } from "react-router-dom"; -import toast from "react-hot-toast"; - -type BottomSheetProps = { - open: boolean; - weekParam: string | null; - changeMenu: ( - id: number, - title: string, - type: "RECIPE" | "TRANSFORMED_RECIPE", - ) => void; - onSubmit: () => void; -}; -export default function BottomSheet({ - open, - weekParam, - changeMenu, - onSubmit, -}: BottomSheetProps) { - const [tab, setTab] = useState<"common" | "recommend">("common"); - const [isOpen, setIsOpen] = useState(false); - const [isModalOpen, setIsModalOpen] = useState(false); - const [selected, setSelected] = useState<{ - id: number; - title: string; - type: "RECIPE" | "TRANSFORMED_RECIPE"; - } | null>(null); - const navigate = useNavigate(); - //안전한 순 위시리스트 코드 - const { data: recommendList } = useGetRecommendList("안전한 순"); - - //가족 위시리스트 코드 - const familyRoomId = useFamilyStore.getState().familyRoomId; - const { - data: familyWish, - isFetching, - hasNextPage, - fetchNextPage, - } = useGetInfiniteFamilyWishList(familyRoomId, 6); - // isPending, isError 등은 나중에... - - const { ref, inView } = useInView({ - threshold: 0, - }); - - useEffect(() => { - if (inView && hasNextPage && !isFetching) { - fetchNextPage(); - } - }, [inView, isFetching, hasNextPage, fetchNextPage]); - ///////// - - useEffect(() => { - if (selected !== null) return; - if (tab === "common") { - const first = familyWish?.pages?.[0]; - if (!first) return; - setSelected({ id: first.id, title: first.title, type: first.type }); - } else { - const first = recommendList?.recipes?.[0]; - if (!first) return; - const recipeType = first.transformed ? "TRANSFORMED_RECIPE" : "RECIPE"; - setSelected({ - id: Number(first.id), - title: first.title, - type: recipeType, - }); - } - }, [tab, familyWish, recommendList, selected]); - - useEffect(() => { - setIsOpen(open); - }, [open]); - - //모달창 속 확인 버튼 눌렀을 때 - const handleButton = () => { - if (weekParam === "THIS") { - navigate(`/meal-plan?tab=THIS`); - } else { - navigate(`/meal-plan?tab=NEXT`); - } - onSubmit(); - }; - - const handleChange = () => { - if (!selected) { - toast.error("교체할 메뉴를 선택해주세요"); - return; - } - changeMenu(selected.id, selected.title, selected.type); - setSelected(null); - setIsOpen(false); - }; - - return ( -
-
- {isModalOpen && ( - { - setIsModalOpen(false); - }} - /> - )} - - -
-
- - -
- -
-
- {tab === "common" ? ( - <> - {familyWish?.pages.map((item) => { - const isMenuListSelect = selected?.id === item.id; - return ( - - setSelected({ - id: item.id, - title: item.title, - type: item.type, - }) - } - clickable={true} - /> - ); - })} - - ) : ( - <> - {recommendList?.recipes.map((item) => { - const recipeType = item.transformed - ? "TRANSFORMED_RECIPE" - : "RECIPE"; - const isMenuListSelect = selected?.id === Number(item.id); - return ( - - setSelected({ - id: Number(item.id), - title: item.title, - type: recipeType, - }) - } - clickable={true} - /> - ); - })} - - )} -
-
-
- -
-
-
- ); -} diff --git a/src/components/meal-plan/ChooseWeek.tsx b/src/components/meal-plan/ChooseWeek.tsx deleted file mode 100644 index 44307726..00000000 --- a/src/components/meal-plan/ChooseWeek.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useEffect, useState } from "react"; -import { type DayOfWeek, type SlotRequest } from "../../types/meal-plan"; -import CalenderChip from "./CalendarChip/CalendarChipS"; - -type ChooseWeekProps = { - mealTime: "점심" | "저녁"; - onChangeSelected: (slots: SlotRequest[]) => void; -}; - -const week: { label: string; value: DayOfWeek }[] = [ - { label: "월", value: "MONDAY" }, - { label: "화", value: "TUESDAY" }, - { label: "수", value: "WEDNESDAY" }, - { label: "목", value: "THURSDAY" }, - { label: "금", value: "FRIDAY" }, - { label: "토", value: "SATURDAY" }, - { label: "일", value: "SUNDAY" }, -]; - -export default function ChooseWeek({ - mealTime, - onChangeSelected, -}: ChooseWeekProps) { - const mealType = mealTime === "점심" ? "LUNCH" : "DINNER"; - - const [selectedDays, setSelectedDays] = useState([]); - - const toggleDay = (day: DayOfWeek) => { - setSelectedDays((prev) => - prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day], - ); - }; - - useEffect(() => { - const slots: SlotRequest[] = selectedDays.map((d) => ({ - dayOfWeek: d, - mealType, - })); - onChangeSelected(slots); - }, [selectedDays, mealType, onChangeSelected]); - - return ( -
-

{mealTime} 식사

-
- {week.map((day, idx) => ( - toggleDay(day.value)} - /> - ))} -
-
- ); -} diff --git a/src/components/meal-plan/ListHeader.tsx b/src/components/meal-plan/ListHeader.tsx deleted file mode 100644 index 6e5f3049..00000000 --- a/src/components/meal-plan/ListHeader.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import Chevron from "../common/icon/Chevron"; - -type ListHeaderProps = { - toggleable?: boolean; - title: string; - date: string; - isOpen?: boolean; - setIsOpen?: () => void; -}; - -export default function ListHeader({ - toggleable = false, - title, - date, - isOpen = true, - setIsOpen, -}: ListHeaderProps) { - return ( -
- {toggleable && ( - - )} -

{title}

-

{date}

-
- ); -} diff --git a/src/components/meal-plan/MealPlanResult.tsx b/src/components/meal-plan/MealPlanResult.tsx deleted file mode 100644 index 8d3874ca..00000000 --- a/src/components/meal-plan/MealPlanResult.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import TryAgain from "../../assets/icons/try-again.svg"; -import CalendarChipM from "../../components/meal-plan/CalendarChip/CalendarChipM"; -import MenuChip from "../../components/meal-plan/MenuChip"; -import AlertModal from "../../components/common/AlertModal"; -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; -import type { SlotItem } from "../../types/meal-plan"; -import { useFamilyStore } from "../../stores/use-family-store"; -import { postConfirmMealPlan } from "../../api/meal-plan"; -import toast from "react-hot-toast"; - -type MealPlanResultProps = { - mealPlanId: number; - onClick: () => void; - weekParam: string | null; - isLoading: boolean; -}; - -type mealPlanResponse = Record; -const dayKor: Record = { - MONDAY: "월", - TUESDAY: "화", - WEDNESDAY: "수", - THURSDAY: "목", - FRIDAY: "금", - SATURDAY: "토", - SUNDAY: "일", -}; - -export default function MealPlanResult({ - mealPlanId, - onClick, - weekParam, - isLoading, -}: MealPlanResultProps) { - const navigate = useNavigate(); - const [isOpen, setIsOpen] = useState(false); - - const response = sessionStorage.getItem("mealPlan"); - if (isLoading) return null; - if (!response) { - toast.error("올바른 접근이 아닙니다. 식단 생성부터 해주세요!"); - return; - } - const data = JSON.parse(response) as mealPlanResponse; - - const handleButton = () => { - setIsOpen(true); - }; - const { familyRoomId } = useFamilyStore.getState(); - - const handleClick = async () => { - try { - if (familyRoomId == null) { - toast.error("familyRoomId 없음"); - return; - } - await postConfirmMealPlan({ - familyRoomId: familyRoomId, - mealPlanId: mealPlanId, - }); - } catch (error) { - alert("주간 식단 확정 실패" + error); - } finally { - if (weekParam === "THIS") { - navigate(`/meal-plan?tab=THIS`); - } else { - navigate(`/meal-plan?tab=NEXT`); - } - } - }; - return ( -
- {isOpen && ( - setIsOpen(false)} - /> - )} -

- 우리가족을 위한 식단표가 {"\n"}생성되었어요. -

-
- -
-
-
- -

- 점심 -

-

- 저녁 -

-
- {Object.entries(data).map(([day, slots]) => { - const date = dayKor[day] ?? day; - const lunch = slots.find((slot) => slot.mealType === "LUNCH"); - const dinner = slots.find((slot) => slot.mealType === "DINNER"); - return ( -
- - {lunch ? ( - - ) : ( -
- )} - {dinner ? ( - - ) : ( -
- )} -
- ); - })} -
-
- - -
-
- ); -} diff --git a/src/components/meal-plan/MemberMealPlanView.tsx b/src/components/meal-plan/MemberMealPlanView.tsx deleted file mode 100644 index 49d588d0..00000000 --- a/src/components/meal-plan/MemberMealPlanView.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import Background from "../../assets/icons/member-meal-background.svg"; - -export default function MemberMealPlanView() { - return ( -
-

- 방장이 식단을 생성하고 있어요. -

-

- 가족원의 위시리스트를 기반으로 {"\n"}다음주 식단이 생성돼요. -

- 가족원 식단화면 배경 아이콘 -
- ); -} diff --git a/src/components/meal-plan/ReviewModal.tsx b/src/components/meal-plan/ReviewModal.tsx deleted file mode 100644 index 17c05ab5..00000000 --- a/src/components/meal-plan/ReviewModal.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { useState } from "react"; -import X from "../../assets/icons/x-icon.svg"; -import Button from "../common/Button"; -import UnselectedStar from "../../assets/icons/star-unselected.svg"; -import SelectedStar from "../../assets/icons/star-selected.svg"; -import AlertModal from "../common/AlertModal"; -import type { createReview } from "../../types/review"; -import usePostReview from "../../hooks/mutations/use-post-review"; -import toast from "react-hot-toast"; - -type ReviewModalProps = { - recipeId: number; - onClick: () => void; - type: "TRANSFORMED_RECIPE" | "RECIPE"; -}; - -type Preference = boolean | null; - -export default function ReviewModal({ - recipeId, - onClick, - type, -}: ReviewModalProps) { - const [preference, setPreference] = useState(null); - const star = [1, 2, 3, 4, 5]; - const [score, setScore] = useState(0); - const [isOpen, setIsOpen] = useState(false); - - const handlebutton = (select: Preference) => { - if (select == preference) { - setPreference(null); - } else { - setPreference(select); - } - }; - - const onModalClick = () => { - setIsOpen(false); - onClick(); - }; - - const { mutate } = usePostReview(); - - const handleSubmitReview = ({ - recipeId, - score, - isFavorite, - }: createReview) => { - mutate( - { recipeId, score, isFavorite, type }, - { - onSuccess: () => { - setIsOpen(true); - }, - onError: (e: any) => { - if (!score) { - toast.error("별을 클릭해주세요"); - return; - } - toast.error(e.response?.data?.message); - onClick(); - }, - }, - ); - }; - return ( -
- {isOpen && ( - - )} -
- -
-

오늘의 메뉴는 어떠셨나요

-
- {star.map((idx) => ( - - ))} -
-
- - -
-
-
-
-
-
- ); -} diff --git a/src/components/meal-plan/TodayMeal.tsx b/src/components/meal-plan/TodayMeal.tsx deleted file mode 100644 index 962f3840..00000000 --- a/src/components/meal-plan/TodayMeal.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useState } from "react"; -import Button from "../common/Button"; -import ReviewModal from "./ReviewModal"; -import type { TodayMeal } from "../../types/meal-plan"; -import IngredientAndRecipe from "../common/IngredientAndRecipe"; - -export default function TodayMeal({ data }: { data: TodayMeal }) { - const [isDone, setIsDone] = useState(false); - const [isOpen, setIsOpen] = useState(false); - const handleOpen = () => setIsOpen(false); - return ( - <> -
- {isOpen && ( - - )} -
- {/*

우유대신,

*/} - -

- {data.title.split(" ").map((title, idx) => ( - - {title} {idx % 2 === 1 &&
} -
- ))} -

-
- - {`${data.title} -
- {!data.isReviewed && ( -
- {!isDone ? ( -
- )} -
- -
- - ); -} diff --git a/src/components/meal-plan/WeekMeal.tsx b/src/components/meal-plan/WeekMeal.tsx deleted file mode 100644 index 0cf421d8..00000000 --- a/src/components/meal-plan/WeekMeal.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { useNavigate } from "react-router-dom"; -import useGetWeekMealPlan from "../../hooks/queries/use-get-week-meal"; -import { useFamilyStore } from "../../stores/use-family-store"; -import { useProfileStore } from "../../stores/use-profile-store"; -import { changeAdditionalProp } from "../../utils/changeAdditionalProp"; -import { getNextMonday, getThisMonday, getWeekOfMonth } from "../../utils/date"; -import EmptyState from "../common/EmptyState"; -import CalendarChipS from "./CalendarChip/CalendarChipS"; -import DateMenuList from "./DateMenuList"; -import ListHeader from "./ListHeader"; -import { LoadingSpinner } from "../common/LoadingSpinner"; - -export type weekMealProps = { - weekType: "THIS" | "NEXT"; -}; -export default function WeekMeal({ weekType }: weekMealProps) { - const { familyRoomId } = useFamilyStore(); - const isLeader = useProfileStore().isLeader; - const baseDate = new Date(); - const date = - weekType === "THIS" ? getThisMonday(baseDate) : getNextMonday(baseDate); //이번주/다음주 시작 월요일 날짜 - const { data, isError, isLoading } = useGetWeekMealPlan(familyRoomId!, date); - - const navigate = useNavigate(); - if (isError) { - return ( -
- {isLeader ? ( - navigate(`/meal-plan/create?week=${weekType}`)} - /> - ) : ( - - )} -
- ); - } - if (isLoading) { - return ( -
- -
- ); - } - const weekData = changeAdditionalProp(data?.result.slots || {}, "WEEK"); - const listHeaderDate = - date + "~" + date[5] + date[6] + "." + (Number(date[8] + date[9]) + 6); - const { month, weekKor } = getWeekOfMonth(new Date(date)); - - const dayKorMap: Record = { - MONDAY: "월", - TUESDAY: "화", - WEDNESDAY: "수", - THURSDAY: "목", - FRIDAY: "금", - SATURDAY: "토", - SUNDAY: "일", - }; - const dayNames = [ - "MONDAY", - "TUESDAY", - "WEDNESDAY", - "THURSDAY", - "FRIDAY", - "SATURDAY", - "SUNDAY", - ]; - const todayIdx = new Date().getDay(); - - const adjustedIdx = - weekType === "THIS" ? (todayIdx === 0 ? 6 : todayIdx - 1) : 0; - - //오늘~앞으로 날 - const futureDays = dayNames.slice(adjustedIdx).map((day) => ({ - dayKor: dayKorMap[day], - meals: weekData[day] || [], - })); - - //지난 날 - const pastDays = dayNames.slice(0, adjustedIdx).map((day) => ({ - dayKor: dayKorMap[day], - meals: weekData[day] || [], - })); - return ( - <> -
- -
-
- {futureDays.map((day) => { - if (day.meals.length > 0) { - return ( -
- - -
- ); - } - })} - {pastDays.map((day) => { - if (day.meals.length > 0) { - return ( -
- - -
- ); - } - })} -
- - ); -} diff --git a/src/layouts/mobile-layout.tsx b/src/layouts/mobile-layout.tsx index 3f148e54..d40cb9a4 100644 --- a/src/layouts/mobile-layout.tsx +++ b/src/layouts/mobile-layout.tsx @@ -1,6 +1,9 @@ -import { Outlet, useLocation } from "react-router-dom"; -import Footer from "../components/common/Footer"; +import { Outlet, useLocation, useNavigate } from "react-router-dom"; +import Background from "../assets/icons/footer-background.svg"; +import Home from "../assets/icons/home.svg"; +import Note from "../assets/icons/note-edit.svg"; import UpButton from "../components/common/UpButton"; +import CalendarIcon from "../components/common/icon/Calendar"; export default function MobileLayout() { const location = useLocation(); @@ -8,11 +11,54 @@ export default function MobileLayout() { const showFooter = footerPaths.includes(location.pathname); + const navigate = useNavigate(); + return (
- {showFooter &&
} + {showFooter && ( +
+
+ 푸터 배경 + +
+
+ +
+ +
+ +
+
+ + +
+
+ )}
diff --git a/src/pages/meal-plan/meal-plan-create-page.tsx b/src/pages/meal-plan/meal-plan-create-page.tsx index ade4b673..d1684086 100644 --- a/src/pages/meal-plan/meal-plan-create-page.tsx +++ b/src/pages/meal-plan/meal-plan-create-page.tsx @@ -1,12 +1,14 @@ import Button from "../../components/common/Button"; -import ChooseWeek from "../../components/meal-plan/ChooseWeek"; -import MemberMealPlanView from "../../components/meal-plan/MemberMealPlanView"; import PublicHeader from "../../components/header/PublicHeader"; -import { useState } from "react"; -import type { CreateMealPlan, SlotRequest } from "../../types/meal-plan"; -import { postCreateMealPlans } from "../../api/meal-plan"; +import { useEffect, useState } from "react"; +import type { + CreateMealPlan, + DayOfWeek, + SlotItem, + SlotRequest, +} from "../../types/meal-plan"; +import { postConfirmMealPlan, postCreateMealPlans } from "../../api/meal-plan"; import { useFamilyStore } from "../../stores/use-family-store"; -import MealPlanResult from "../../components/meal-plan/MealPlanResult"; import { changeAdditionalProp } from "../../utils/changeAdditionalProp"; import AlertModal from "../../components/common/AlertModal"; import { useNavigate, useSearchParams } from "react-router-dom"; @@ -14,6 +16,11 @@ import { getNextMonday, getThisMonday } from "../../utils/date"; import { LoadingSpinner } from "../../components/common/LoadingSpinner"; import toast from "react-hot-toast"; import { useProfileStore } from "../../stores/use-profile-store"; +import CalendarChipS from "../../components/chip/CalendarChip/CalendarChipS"; +import Background from "../../assets/icons/member-meal-background.svg"; +import CalendarChipM from "../../components/chip/CalendarChip/CalendarChipM"; +import MenuChip from "../../components/chip/MenuChip"; +import TryAgain from "../../assets/icons/try-again.svg"; const MealPlanCreatePage = () => { const [step, setStep] = useState<"create" | "result">("create"); @@ -151,3 +158,205 @@ const MealPlanCreatePage = () => { }; export default MealPlanCreatePage; + +type ChooseWeekProps = { + mealTime: "점심" | "저녁"; + onChangeSelected: (slots: SlotRequest[]) => void; +}; + +const week: { label: string; value: DayOfWeek }[] = [ + { label: "월", value: "MONDAY" }, + { label: "화", value: "TUESDAY" }, + { label: "수", value: "WEDNESDAY" }, + { label: "목", value: "THURSDAY" }, + { label: "금", value: "FRIDAY" }, + { label: "토", value: "SATURDAY" }, + { label: "일", value: "SUNDAY" }, +]; + +function ChooseWeek({ mealTime, onChangeSelected }: ChooseWeekProps) { + const mealType = mealTime === "점심" ? "LUNCH" : "DINNER"; + + const [selectedDays, setSelectedDays] = useState([]); + + const toggleDay = (day: DayOfWeek) => { + setSelectedDays((prev) => + prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day], + ); + }; + + useEffect(() => { + const slots: SlotRequest[] = selectedDays.map((d) => ({ + dayOfWeek: d, + mealType, + })); + onChangeSelected(slots); + }, [selectedDays, mealType, onChangeSelected]); + + return ( +
+

{mealTime} 식사

+
+ {week.map((day, idx) => ( + toggleDay(day.value)} + /> + ))} +
+
+ ); +} + +//방장이 아닌 가족원이 이 페이지에 진입했을 때 +function MemberMealPlanView() { + return ( +
+

+ 방장이 식단을 생성하고 있어요. +

+

+ 가족원의 위시리스트를 기반으로 {"\n"}다음주 식단이 생성돼요. +

+ 가족원 식단화면 배경 아이콘 +
+ ); +} + +//식단 생성 결과 +type MealPlanResultProps = { + mealPlanId: number; + onClick: () => void; + weekParam: string | null; + isLoading: boolean; +}; + +type mealPlanResponse = Record; +const dayKor: Record = { + MONDAY: "월", + TUESDAY: "화", + WEDNESDAY: "수", + THURSDAY: "목", + FRIDAY: "금", + SATURDAY: "토", + SUNDAY: "일", +}; + +function MealPlanResult({ + mealPlanId, + onClick, + weekParam, + isLoading, +}: MealPlanResultProps) { + const navigate = useNavigate(); + const [isOpen, setIsOpen] = useState(false); + + const response = sessionStorage.getItem("mealPlan"); + if (isLoading) return null; + if (!response) { + toast.error("올바른 접근이 아닙니다. 식단 생성부터 해주세요!"); + return; + } + const data = JSON.parse(response) as mealPlanResponse; + + const handleButton = () => { + setIsOpen(true); + }; + const { familyRoomId } = useFamilyStore.getState(); + + const handleClick = async () => { + try { + if (familyRoomId == null) { + toast.error("familyRoomId 없음"); + return; + } + await postConfirmMealPlan({ + familyRoomId: familyRoomId, + mealPlanId: mealPlanId, + }); + } catch (error) { + alert("주간 식단 확정 실패" + error); + } finally { + if (weekParam === "THIS") { + navigate(`/meal-plan?tab=THIS`); + } else { + navigate(`/meal-plan?tab=NEXT`); + } + } + }; + return ( +
+ {isOpen && ( + setIsOpen(false)} + /> + )} +

+ 우리가족을 위한 식단표가 {"\n"}생성되었어요. +

+
+ +
+
+
+ +

+ 점심 +

+

+ 저녁 +

+
+ {Object.entries(data).map(([day, slots]) => { + const date = dayKor[day] ?? day; + const lunch = slots.find((slot) => slot.mealType === "LUNCH"); + const dinner = slots.find((slot) => slot.mealType === "DINNER"); + return ( +
+ + {lunch ? ( + + ) : ( +
+ )} + {dinner ? ( + + ) : ( +
+ )} +
+ ); + })} +
+
+ + +
+
+ ); +} diff --git a/src/pages/meal-plan/meal-plan-edit-page.tsx b/src/pages/meal-plan/meal-plan-edit-page.tsx index 535bab06..ef32a924 100644 --- a/src/pages/meal-plan/meal-plan-edit-page.tsx +++ b/src/pages/meal-plan/meal-plan-edit-page.tsx @@ -1,8 +1,7 @@ import { useEffect, useRef, useState } from "react"; import PublicHeader from "../../components/header/PublicHeader"; -import CalendarChipM from "../../components/meal-plan/CalendarChip/CalendarChipM"; -import MenuChip from "../../components/meal-plan/MenuChip"; -import BottomSheet from "../../components/meal-plan/BottomSheet"; +import CalendarChipM from "../../components/chip/CalendarChip/CalendarChipM"; +import MenuChip from "../../components/chip/MenuChip"; import { useSearchParams } from "react-router-dom"; import { type Updates, @@ -14,6 +13,13 @@ import { useFamilyStore } from "../../stores/use-family-store"; import { patchEditMealPlans, postConfirmMealPlan } from "../../api/meal-plan"; import { useNavigate } from "react-router-dom"; import toast from "react-hot-toast"; +import Button from "../../components/common/Button"; +import MenuList from "../../components/common/MenuList"; +import Chevron from "../../components/common/icon/Chevron"; +import AlertModal from "../../components/common/AlertModal"; +import useGetInfiniteFamilyWishList from "../../hooks/queries/use-get-infinite-family-wishlist"; +import { useInView } from "react-intersection-observer"; +import { useGetRecommendList } from "../../hooks/queries/use-get-recommendations"; type mealPlanResponse = Record; @@ -213,3 +219,222 @@ const MealPlanEditPage = () => { }; export default MealPlanEditPage; + +//bottomSheet +type BottomSheetProps = { + open: boolean; + weekParam: string | null; + changeMenu: ( + id: number, + title: string, + type: "RECIPE" | "TRANSFORMED_RECIPE", + ) => void; + onSubmit: () => void; +}; +function BottomSheet({ + open, + weekParam, + changeMenu, + onSubmit, +}: BottomSheetProps) { + const [tab, setTab] = useState<"common" | "recommend">("common"); + const [isOpen, setIsOpen] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selected, setSelected] = useState<{ + id: number; + title: string; + type: "RECIPE" | "TRANSFORMED_RECIPE"; + } | null>(null); + const navigate = useNavigate(); + //안전한 순 위시리스트 코드 + const { data: recommendList } = useGetRecommendList("안전한 순"); + + //가족 위시리스트 코드 + const familyRoomId = useFamilyStore.getState().familyRoomId; + const { + data: familyWish, + isFetching, + hasNextPage, + fetchNextPage, + } = useGetInfiniteFamilyWishList(familyRoomId, 6); + + const { ref, inView } = useInView({ + threshold: 0, + }); + + useEffect(() => { + if (inView && hasNextPage && !isFetching) { + fetchNextPage(); + } + }, [inView, isFetching, hasNextPage, fetchNextPage]); + + useEffect(() => { + if (selected !== null) return; + if (tab === "common") { + const first = familyWish?.pages?.[0]; + if (!first) return; + setSelected({ id: first.id, title: first.title, type: first.type }); + } else { + const first = recommendList?.recipes?.[0]; + if (!first) return; + const recipeType = first.transformed ? "TRANSFORMED_RECIPE" : "RECIPE"; + setSelected({ + id: Number(first.id), + title: first.title, + type: recipeType, + }); + } + }, [tab, familyWish, recommendList, selected]); + + useEffect(() => { + setIsOpen(open); + }, [open]); + + //모달창 속 확인 버튼 눌렀을 때 + const handleButton = () => { + if (weekParam === "THIS") { + navigate(`/meal-plan?tab=THIS`); + } else { + navigate(`/meal-plan?tab=NEXT`); + } + onSubmit(); + }; + + const handleChange = () => { + if (!selected) { + toast.error("교체할 메뉴를 선택해주세요"); + return; + } + changeMenu(selected.id, selected.title, selected.type); + setSelected(null); + setIsOpen(false); + }; + + return ( +
+
+ {isModalOpen && ( + { + setIsModalOpen(false); + }} + /> + )} + + +
+
+ + +
+ +
+
+ {tab === "common" ? ( + <> + {familyWish?.pages.map((item) => { + const isMenuListSelect = selected?.id === item.id; + return ( + + setSelected({ + id: item.id, + title: item.title, + type: item.type, + }) + } + clickable={true} + /> + ); + })} + + ) : ( + <> + {recommendList?.recipes.map((item) => { + const recipeType = item.transformed + ? "TRANSFORMED_RECIPE" + : "RECIPE"; + const isMenuListSelect = selected?.id === Number(item.id); + return ( + + setSelected({ + id: Number(item.id), + title: item.title, + type: recipeType, + }) + } + clickable={true} + /> + ); + })} + + )} +
+
+
+ +
+
+
+ ); +} diff --git a/src/pages/meal-plan/meal-plan-page.tsx b/src/pages/meal-plan/meal-plan-page.tsx index 43ed344f..6771c004 100644 --- a/src/pages/meal-plan/meal-plan-page.tsx +++ b/src/pages/meal-plan/meal-plan-page.tsx @@ -1,8 +1,6 @@ import { useEffect, useState } from "react"; import HomeHeader from "../../components/header/HomeHeader"; -import TodayMeal from "../../components/meal-plan/TodayMeal"; -import WeekMeal from "../../components/meal-plan/WeekMeal"; -import { useSearchParams } from "react-router-dom"; +import { useNavigate, useSearchParams } from "react-router-dom"; import selectedSun from "../../assets/icons/sun-selected.svg"; import unselectedSun from "../../assets/icons/sun-unselected.svg"; import selectedMoon from "../../assets/icons/moon-selected.svg"; @@ -11,6 +9,22 @@ import useGetTodayMealPlan from "../../hooks/queries/use-get-today-meal-plan"; import { useFamilyStore } from "../../stores/use-family-store"; import EmptyState from "../../components/common/EmptyState"; import { LoadingSpinner } from "../../components/common/LoadingSpinner"; +import IngredientAndRecipe from "../../components/common/IngredientAndRecipe"; +import Button from "../../components/common/Button"; +import type { TodayMeal } from "../../types/meal-plan"; +import { useProfileStore } from "../../stores/use-profile-store"; +import { getNextMonday, getThisMonday, getWeekOfMonth } from "../../utils/date"; +import useGetWeekMealPlan from "../../hooks/queries/use-get-week-meal"; +import { changeAdditionalProp } from "../../utils/changeAdditionalProp"; +import CalendarChipS from "../../components/chip/CalendarChip/CalendarChipS"; +import DateMenuList from "../../components/meal-plan/DateMenuList"; +import usePostReview from "../../hooks/mutations/use-post-review"; +import type { createReview } from "../../types/review"; +import toast from "react-hot-toast"; +import AlertModal from "../../components/common/AlertModal"; +import UnselectedStar from "../../assets/icons/star-unselected.svg"; +import SelectedStar from "../../assets/icons/star-selected.svg"; +import X from "../../assets/icons/x-icon.svg"; const MealPlanPage = () => { const [searchParams] = useSearchParams(); @@ -103,17 +117,313 @@ const MealPlanPage = () => {
) : ( - + )} )} )} - {tab == "이번주 식단" && } - {tab == "다음주 식단" && } + {tab == "이번주 식단" && ( + + )} + {tab == "다음주 식단" && ( + + )}
); }; export default MealPlanPage; + +// 오늘의 식단 탭 +function TodayMealTab({ data }: { data: TodayMeal }) { + const [isDone, setIsDone] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const handleClose = () => setIsOpen(false); + return ( + <> +
+ {isOpen && ( + + )} +
+ {/*

우유대신,

*/} + {/*현재 부연설명이 들어오고 있지 않아서 주석 처리 */} +

+ {data.title.split(" ").map((title, idx) => ( + + {title} {idx % 2 === 1 &&
} +
+ ))} +

+
+ + {`${data.title} +
+ {!data.isReviewed && ( +
+ {!isDone ? ( +
+ )} +
+ +
+ + ); +} + +// 이번주/다음주 탭 +function WeekMealTab({ weekType }: { weekType: "THISWEEK" | "NEXTWEEK" }) { + const { familyRoomId } = useFamilyStore(); + const isLeader = useProfileStore().isLeader; + const baseDate = new Date(); + const date = + weekType === "THISWEEK" ? getThisMonday(baseDate) : getNextMonday(baseDate); //이번주/다음주 시작 월요일 날짜 + const { data, isError, isLoading } = useGetWeekMealPlan(familyRoomId!, date); + + const navigate = useNavigate(); + if (isError) { + return ( +
+ {isLeader ? ( + navigate(`/meal-plan/create?week=${weekType}`)} + /> + ) : ( + + )} +
+ ); + } + if (isLoading) { + return ( +
+ +
+ ); + } + const weekData = changeAdditionalProp(data?.result.slots || {}, "WEEK"); + const listHeaderDate = + date + "~" + date[5] + date[6] + "." + (Number(date[8] + date[9]) + 6); + const { month, weekKor } = getWeekOfMonth(new Date(date)); + + const dayKorMap: Record = { + MONDAY: "월", + TUESDAY: "화", + WEDNESDAY: "수", + THURSDAY: "목", + FRIDAY: "금", + SATURDAY: "토", + SUNDAY: "일", + }; + const dayNames = [ + "MONDAY", + "TUESDAY", + "WEDNESDAY", + "THURSDAY", + "FRIDAY", + "SATURDAY", + "SUNDAY", + ]; + const todayIdx = new Date().getDay(); + + const adjustedIdx = + weekType === "THISWEEK" ? (todayIdx === 0 ? 6 : todayIdx - 1) : 0; + + //오늘~앞으로 날 + const futureDays = dayNames.slice(adjustedIdx).map((day) => ({ + dayKor: dayKorMap[day], + meals: weekData[day] || [], + })); + + //지난 날 + const pastDays = dayNames.slice(0, adjustedIdx).map((day) => ({ + dayKor: dayKorMap[day], + meals: weekData[day] || [], + })); + return ( + <> +
+
+

{`${month}월 ${weekKor}`}

+

+ {listHeaderDate} +

+
+
+
+ {futureDays.map((day) => { + if (day.meals.length > 0) { + return ( +
+ + +
+ ); + } + })} + {pastDays.map((day) => { + if (day.meals.length > 0) { + return ( +
+ + +
+ ); + } + })} +
+ + ); +} + +//리뷰 모달 +type ReviewModalProps = { + recipeId: number; + onClick: () => void; + type: "TRANSFORMED_RECIPE" | "RECIPE"; +}; + +type Preference = boolean | null; + +function ReviewModal({ recipeId, onClick, type }: ReviewModalProps) { + const [preference, setPreference] = useState(null); + const star = [1, 2, 3, 4, 5]; + const [score, setScore] = useState(0); + const [isOpen, setIsOpen] = useState(false); + + const handlebutton = (select: Preference) => { + if (select == preference) { + setPreference(null); + } else { + setPreference(select); + } + }; + + const onModalClick = () => { + setIsOpen(false); + onClick(); + }; + + const { mutate } = usePostReview(); + + const handleSubmitReview = ({ + recipeId, + score, + isFavorite, + }: createReview) => { + mutate( + { recipeId, score, isFavorite, type }, + { + onSuccess: () => { + setIsOpen(true); + }, + onError: (e: any) => { + if (!score) { + toast.error("별을 클릭해주세요"); + return; + } + toast.error(e.response?.data?.message); + onClick(); + }, + }, + ); + }; + return ( +
+ {isOpen && ( + + )} +
+ +
+

오늘의 메뉴는 어떠셨나요

+
+ {star.map((idx) => ( + + ))} +
+
+ + +
+
+
+
+
+
+ ); +}